讲这个东西之前先普及两个概念:线程安全的原子性和可见性
原子性:在一个命令或者命令集合执行的同时,没有其他线程在执行相同的代码块,也就是说这些代码块只能同时被一个线程执行。我们称该代码块具有原子性。
可见性:在一个线程执行完成某个命令后,所发生的变化能够被其他线程看到,我们称该代码块具有可见性。
synchronized (lockObject) {
// update object state
}
synchronized关键字声明的代码块就兼具原子性和可见性。
在一般的情况下synchronized已经执行的很好了,但是它还是有一些弱点:
1.它无法中断一个正在等候获得锁的线程。(没有超时和中断机制)
2.锁的同步和释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行。(通俗点就是,什么方法中里获得锁,必须要在什么方法中释放)
3.在解决锁争抢的时候不够灵活。(不能排队只能挣抢)
基于这几点ReentrantLock应运而生。
Lock lock = new ReentrantLock();
lock.lock();
try {
// update object state
}
finally {
lock.unlock();
}
特点:
它和synchronized关键字具有相同的功能,但是使用起来更加的灵活。
1.它拥有与 synchronized 相同的并发性和内存语义。
2.增加了锁投票、定时锁等候和可中断锁等候的一些特性。(在获取锁时可设置中断或者超时)
3.在激烈争用情况下更佳的性能。(减少了JVM线程切换的负担,更加轻量,所以吞吐率会相对较高)
4.可设置锁的争抢方式。(ReentrantLock 构造器的一个参数是 boolean 值,它允许您选择想要一个 公平(fair)锁,还是一个 不公平(unfair)锁。公平锁使线程按照请求锁的顺序依次获得锁;而不公平锁则允许锁争抢,线程有时可以比先请求锁的其他线程先得到锁。)
5.更灵活的使用方式。(例如可以在方法中申请锁,在方法外释放锁)
注意:ReentrantLock在使用的时候要注意一定要让锁成功释放。否则出问题之后必须要花费很大力气才能定位到问题,切记切记。
虽然ReentrantLock拥有很多优势,但是还是不要抛弃使用synchronized,首先synchronized代码块中的锁都是JVM释放,所以不会有手动释放锁的问题,第二 当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。
那什么时候我们使用ReentrantLock呢,很简单当需要ReentrantLock特性的时候,比如需要锁中断,锁超时控制,或者需要设置锁获取顺序。还有就是使用synchronized关键字造成的锁争用成为程序性能瓶颈的时候。否则在平时还是使用synchronized,其实java并没有抛弃这个synchronized关键字,一直在做不断的优化,性能上也在不断提升。所以,能用synchronized,就用synchronized,实在遇到synchronized解决不了的问题时再尝试使用ReentrantLock解决。
网友评论