JAVA并发编程——Synchronized与Lock的区别以及Lock的使用
1.Synchronized与Lock的区别
2.Condition的基础概念
3.使用Condition循环顺序打印ABC
1.Synchronized与Lock的区别
我们可能对于Condition类都比较陌生,所以我们从我们比较熟悉的Synchronized开始对比着学习。
我们都知道Synchronized都有下图这三个使用方法:
文章图片
首先是我们已知的最熟练的synchronized关键字,他是保证线程同步用的,然后是Thread.notify()(唤醒所有正在等待中的线程),Thread.wait()(将该线程加入等待队列)。
但是我们的Lock接口也有类似的三个方法:
文章图片
其中,lock()方法对应着synchronize的sync,Lock接口下的condition(我们将在下文进行介绍)中有等同于notify()的signal(),和wait()的await()。
所以总结地说,Synchronized和Lock有以下区别。
1.原始构成:
synchronized是关键字,属于JVM层面
lock是具体类,是api层面
2.使用方法
synchronized不需要用户区手动释放锁,除非运行完毕或者抛异常
lock需要用户手动去释放锁,就有可能出现死锁现象,一般配合try finally来释放锁。
3.加锁是否公平
synchronized:非公平
lock:非公平(构造方法可选择,默认非公平)
4.等待是否可中断
synchronized不可中断,除非执行完毕或者抛出异常
reentrantlock可中断,可设置超时方法,设置interrupt方法
5,绑定condition
synchronized不能绑定
condition用来实现分组所需要的唤醒的线程们,可以精确唤醒,而不是像synchronized随机唤醒一个,或者唤醒全部的线程。
2.Condition的基础概念
从上文刚刚简单的介绍可以看出,condition是用来精确唤醒某一个线程的,接下来我们就来系统地介绍一下condition:
Cindition,它是用来代替传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。
3.使用Condition循环顺序打印ABC
既然Condition是用来精确唤醒线程的,那么我们接下来实现一个小Demo,创建三个线程,让他们按顺序循环打印ABC,A打印5次,B打印10次,C打印15次,并且使用Condition精确唤醒:
我们将要用到一下变量:
//一个信号量 1代表打印A的线程应该被唤醒,2代表B,3代表C
private int number = 1;
//一把锁
private Lock lock = new ReentrantLock();
//c1,c2,c3用来精确唤醒的线程
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
接下来我们定义三个方法:
public void print5() {
}public void print10() {
}public void print15() {
}
我们将会启动多个线程,分别运行print5(),print10(),print15()方法,
通过condition控制,在print5()结束后,启动print10(),print10()结束后,启动print15()方法,一直循环。
接下分别来看一下三个函数的函数体:
public void print5() {
//加锁,每次只有一个线程能运行
lock.lock();
try {
//number作为信号量,为1的时候就应该执行print5()方法
while (number != 1) {
//如果不是1,此线程就等待
c1.await();
}
//执行打印方法
for (int i = 1;
i <= 5;
i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//接下来应该轮到print10()方法启动了
number = 2;
//唤醒print10()所拥有的的线程
c2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}public void print10() {
//加锁,每次只有一个线程能运行
lock.lock();
try {
//number作为信号量,为2的时候就应该执行print10()方法
while (number != 2) {
//如果不是2,此线程就等待
c2.await();
}
for (int i = 1;
i <= 10;
i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//接下来应该轮到print15()方法启动了
number = 3;
//唤醒print15()所拥有的的线程
c3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}public void print15() {
//加锁,每次只有一个线程能运行
lock.lock();
try {
while (number != 3) {
//如果不是3,此线程就等待
c3.await();
}
for (int i = 1;
i <= 15;
i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//接下来又回到print5()方法启动了
number = 1;
//唤醒print5()所在线程
c1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
从注释我们可以看出,通过Lock加锁,可以每次只让一个方法进行,通过number信号量和三个condition,可以精确地唤醒对应地线程,接下来,我们分别创建线程,来执行一下这三个方法:
public static void main(String[] args) {
ShareResources shareResources = new ShareResources();
for (int i = 1;
i < 5;
i++) {
new Thread(() -> {
shareResources.print5();
}, "A").start();
}
for (int i = 1;
i < 5;
i++) {
new Thread(() -> {
shareResources.print10();
}, "B").start();
}
for (int i = 1;
i < 5;
i++) {
new Thread(() -> {
shareResources.print15();
}, "C").start();
}}
再看一下执行结果:
文章图片
执行成功!
【JAVA并发编程——Synchronized与Lock的区别以及Lock的使用】总结:
通过这篇博客,我们总结了Synchronized与Lock的区别,得出synchronized是可以全部换成lock的,而且lock的控制比synchronized更家精确和自由一点,还是用了Lock进行线程的顺序循环输出。
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- python青少年编程比赛_第十一届蓝桥杯大赛青少年创意编程组比赛细则
- Java|Java基础——数组
- RxJava|RxJava 在Android项目中的使用(一)
- java之static、static|java之static、static final、final的区别与应用