回答这个问题,我们要从五个方面说起
- 原始组成
synchronized是关键字属于jvm层面
monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象只有在同步块或方法中才能调用wait/notify等方法)
Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁
- 使用方法
synchronized不需要用户去手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用
ReentrantLock则需要用户去手动释放锁若没有主动释放锁,则有可能导致出现死锁现象
需要lock()和unlock()方法配合try/finally语句块来完成
- 等待是否可中断
synchronized不可中断,除非抛出异常或者正常运行完成
ReentrantLock可中断,1.设置超时方法tryLock(Long timeout, TimeUnit unit)
2.LockInterruptibly()放代码块中,调用interrupt()方法可中断
- 加锁是否公平
synchronized非公平锁
ReentrantLock两者都可以,默认公平锁,构造方法中可以传入boolean值,true为公平锁,false为非公平锁
- 锁绑定多个条件Condition
synchronized没有
ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
概念说到这里,接下来看一个demo,ReentrantLock是怎样精准唤醒线程的,还是多线程操作资源类,创建一个资源类,资源类中分别有打印5次,10次,15次的三个方法,然后创建三个线程,分别调用这三个方法,期待的效果是线程1打印完5次后,线程2打印10次,线程2打印完10次后,线程3打印15次,线程3打印完15次后,线程1在打印5次,就这样循环交替打印,来10轮。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ConditionNotifyThreadDemo {
public static void main(String[] args) {
MyResource myResource = new MyResource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
myResource.print5();
}
}, "t1\t").start();new Thread(() -> {
for (int i = 0; i < 10; i++) {
myResource.print10();
}
}, "t2\t").start();new Thread(() -> {
for (int i = 0; i < 10; i++) {
myResource.print15();
}
}, "t3\t").start();}
}
class MyResource {
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();public void print5() {
try {
lock.lock();
while (number != 1) {
c1.await();
}for (int i = 1; i < 6; i++) {
System.err.println(Thread.currentThread().getName() + "\t" + i);
}
number = 2;c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}}
public void print10() {
try {
lock.lock();
while (number != 2) {
c2.await();
}for (int i = 1; i < 11; i++) {
System.err.println(Thread.currentThread().getName() + "\t" + i);
}
number = 3;c3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}}
public void print15() {
try {
lock.lock();
while (number != 3) {
c3.await();
}for (int i = 1; i < 16; i++) {
System.err.println(Thread.currentThread().getName() + "\t" + i);
}
number = 1;c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}}
}
在资源类(MyResource)中,通过lock的newCondition方法实例化三个条件类(Condition )的实例c1,c2,c3,依次使用在print5,print10,print15方法中,三个打印方法分别通过监视number变量来执行打印任务,print5方法,当number变量为1的时候打印,否则就执行c1的await方法等待,当打印成功后,将变量number设置为2,调用c2的signal方法,这样可以精准的唤醒线程2的调用print10方法,同样,print10方法,当number变量为2的时候打印,否则就执行c2的await方法等待,当打印成功后,将变量number设置为3,调用c3的signal方法,这样可以精准的唤醒线程3的调用print15方法,依次类推,就组成了一个print5→print10→print15→print5...的循环执行链,我们来看看结果
t1 1
t1 2
t1 3
t1 4
t1 5
t2 1
t2 2
t2 3
t2 4
t2 5
t2 6
t2 7
t2 8
t2 9
t2 10
t3 1
t3 2
t3 3
t3 4
t3 5
t3 6
t3 7
t3 8
t3 9
t3 10
t3 11
t3 12
t3 13
t3 14
t3 15
t1 1
t1 2
t1 3
t1 4
t1 5
t2 1
t2 2
t2 3
t2 4
t2 5
t2 6
t2 7
t2 8
t2 9
t2 10
t3 1
t3 2
t3 3
t3 4
t3 5
t3 6
t3 7
t3 8
t3 9
t3 10
t3 11
t3 12
t3 13
t3 14
t3 15......
就这样t1线程打印5次后,t2线程打印10次,t2线程打印完后,t3线程打印15次,循环调用,就这样实现了,多线程间精准唤醒某一个线程,大家可以动手试试,好了,今天文章到这里,下篇见。