ReentrantLock和Synchronized区别

前提背景:
JDK1.5版本之前 lock 的性能比 synchronized 要好,但是之后 synchronized 做了很多的优化(例如加入偏向锁、轻量级锁)之后,synchronized 的性能也追了上来,但是 lock 依旧有它出彩的地方

原因:
在死锁问题的处理上,lock 显得更加灵活,采取的措施也更有效,而 synchronized 只能尽量的去避免死锁的产生,而 lock 可以通过内部的方法来解决死锁

首先来看死锁产生的 4 个必要条件:

(1) 互斥条件:当前线程获取到资源锁,那么其他线程就不能够进入该同步块
(2)不可剥夺条件:当前线程获取到锁了,在执行任务期中,其他线程不可以剥夺它的资源,也就是不能抢占它的资源
(3)保持资源的条件:如果当前线程需要获取两个资源才能获得锁,现在已经获取到一个了,在获取另一个资源时发现,其他线程已经获取了该资源,那么当前线程也要持续的等待其他线程释放该资源,不能够自己释放掉前面获取到的资源
(4)环形回路条件:A线程和B线程都需要获取到两个资源才能获取到锁,A线程和B线程分别已经都获取到一个资源,A线程再去获取另外一个资源时,发现它被B线程获取了,就一直等待B线程释放,B线程也去获取另外一个线程,发现被A获取了,也持续等待A线程释放,类似于这样形成环环相扣的场景

破解死锁:

解除这其中的任意一个条件即可破解死锁,但是第一个条件互斥条件显然不能够破坏,其他三个可以破解:

破解保持资源的条件:一次性的申请所有的内存,这样也就不存在等待的情况
破解不可剥夺条件:当该线程如果获得不到另外一个资源,那么就主动释放该资源
破解环形回路条件:设置顺序来让线程申请,请求资源少的线程先执行,请求资源大的线程后执行,这样前面的资源使用完就会立马释放,也能让后面请求多的线程获取到

这些破解方法里面就第二个:破解不可剥夺条件比较好实现
Lock的破解死锁方法就是主要围绕这一点来实现的

方法一:响应中断

// 支持中断的 API
void lockInterruptibly()

在线程获取不到其他资源要进行阻塞等待的时候,发送中断信号,唤醒它不让它进入阻塞队列,那它就可能会释放掉前面获取到的锁

方法二:支持超时

// 支持超时的 API
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;

如果一定的时间内还没有获取到其他锁,那么就发送一个错误,让当前线程释放掉之前获取到的锁

方法三:非阻塞获取锁

// 支持非阻塞获取锁的 API
boolean tryLock();

获取不到其他锁也不让线程进入阻塞队列,让它直接返回,这样就可能释放掉之前的锁

Lock 和 Synchronized 的区别

相同点:Lock和synchronized 都可以实现锁重入,并且都能保证多线程情况下的共享资源竞争安全,只有一个线程能获取到锁
不同点:
(1)Lock 中锁是可中断的,正式因为它的可中断性才让它在解决死锁的问题上比 synchronized 更有优势,
(2)Lock 可以实现公平锁也可以实现不公平锁,synchronized 只能实现非公平锁
(3)在读取同步状态上,Lock 可以通过 state 这样的 int 变量来判断当前锁的状态,如果 state > 0 说明有线程在使用锁,如果 state == 0 则说明锁没有线程占有,而 synchronized 不能读取当前锁的状态
(4)synchronized 使用更加方便,它的加锁和释放锁都是由编译器来帮助我们完成,而 ReentrantLock 需要我们自己手动来声明加锁 lock() 和释放锁 unLock() 操作,所以未来避免忘记释放锁,最好在 finally 中声明释放
(5)两者在性能上的比较:在 JDK 1.6 之前,在多线程环境下特别是竞争激烈下,synchronized 的效率和吞吐量下降的很严重,而 ReentrantLock 能一直保持在相对稳定的水平上,但是在 JDK1.6 之后加入了大量针对 synchronized 的优化措施,此时 synchronize 的性能和 ReentrantLocj 几乎相差无几,也就是说性能已经不能作为选择 ReentrantLock 而不选择 synchronized 的决定因素了

你可能感兴趣的:(Java)