在我们使用同步块的时候synchronized关键字+wait/notify是我们最常使用的一种代码同步加锁等待唤醒方式,但是在使用过程中我们发现synchronized关键字+wait/notify只能一次性的针对某个条件进行同步加锁,不能跨条件加锁,意思就是说如果有2个地方需要加锁,并且加锁和解锁是相互制约的,那么使用synchronized就不能进行同步加锁,没有办法使用wait/notify进行唤醒操作,因为wait/notify是只能对同一个条件进行唤醒,这个时候我们可以使用到Condition来进行控制,比如我们有一个队列,需要进行队列为空或者满时的等待,如果使用wait/notify的方式是没有办法控制的,因为我们这里提供了take和put方法,并且我们这里还需要精准的控制是等待take还是put的执行,如下代码
public class ConditionTest {
Lock lock = new ReentrantLock();
Condition fullCondition = lock.newCondition();
Condition emptyCondition = lock.newCondition();
Object[] objects = new Object[100];
int take,put,count;
public void put(Object obj) throws InterruptedException {
lock.lock();
try {
while (count == objects.length){
fullCondition.await();
}
objects[put] = obj;
System.out.println("put:"+obj);
if (++put == objects.length){
put = 0;
}
count++;
emptyCondition.signal();
}finally {
lock.unlock();
}
}
public void take() throws InterruptedException {
lock.lock();
try {
while (count == 0){
emptyCondition.await();
}
Object obj = objects[take];
System.out.println("take:"+obj);
if (++take == objects.length){
take = 0;
}
count--;
fullCondition.signal();
}finally {
lock.unlock();
}
}
public static void main(String [] args){
ConditionTest conditionTest = new ConditionTest();
new Thread(() ->{
int i = 0;
for (;;){
try {
conditionTest.put(i);
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (;;){
try {
Thread.sleep(1000);
conditionTest.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
在上面我们精准的控制队列满时那么就让fullCondition条件进行加锁等待,对emptyCondition进行唤醒操作,队列空时就让emptyCondition条件进行加锁等待,对fullCondition进行唤醒操作,因此这个相比wait/notify来说控制的更好了。
我们总结下Condition和wait/notify的比较:
1.Condition可以精准的对多个不同条件进行控制,wait/notify只能和synchronized关键字一起使用,并且只能唤醒一个或者全部的等待队列;
2.Condition需要使用Lock进行控制,使用的时候要注意lock()后及时的unlock(),Condition有类似于await的机制,因此不会产生加锁方式而产生的死锁出现,同时底层实现的是park/unpark的机制,因此也不会产生先唤醒再挂起的死锁,一句话就是不会产生死锁,但是wait/notify会产生先唤醒再挂起的死锁。
在这里同时总结下synchronized关键字和Lock的比较:
synchronized:
1.使用简单,语义清晰,可以在任何地方使用
锁粗化、锁消除、偏向锁、轻量级锁)
3.由JVM进行锁释放,不用手动操作,减少了产生死锁的可能性
如:公平锁、中断锁、超时锁、读写锁、共享锁等
Lock:
1.可以实现所有不能再synchronized关键字中的实现的锁的高级功能
2.可以通过实现Lock接口来实现自定义的一些方法
3.使用时一定要记住unlock锁,如果不释放,容易造成死锁。
这里还有一个读写锁ReadWriteLock简单说明一下,它的实现类ReentrantReadWriteLock,是一个可重入的读写锁,它里面提供了ReadLock和WriteLock,分别用于读时加锁和写时加锁,针对同一个线程先有写锁时,可以获取到读锁,但是先有读锁时不能获取到写锁,必须写锁完成后才能读锁,针对不同线程时,如果已经有读写锁,那么其他线程必须等待当前线程释放读写锁后才能去获取读写锁。