读书笔记:Java并发实战 第13章 显式锁
来源:互联网 发布:linux查看ssh连接数 编辑:程序博客网 时间:2024/06/06 05:22
1、一般用法
<pre name="code" class="java">Lock lock = new ReentrantLock();public void method(){lock.lock();try{//...}finally{lock.unlock();}}
特点:
1、加锁和解锁的方法都是显式的
2、在调用lock()方法时,和进入同步代码块有相同的内存语义
3、在调用unlock()方法时,和退出同步代码块有相同的内存语义
4、独占和可重入
2、轮询锁
先看由于动态的参数顺序造成死锁的例子:
资源类:MyLock
public class MyLock {public Lock lock = new ReentrantLock();public String name;public MyLock(String name){this.name = name;}}
任务类:Executor
public class Executor {public void work(MyLock a,MyLock b){String threadName = Thread.currentThread().getName();a.lock.lock();System.out.println(threadName+"获得"+a.name+"的锁");try{TimeUnit.SECONDS.sleep(1);System.out.println(threadName+"请求"+b.name+"的锁");b.lock.lock();}catch(Exception e){//...}finally{b.lock.unlock();a.lock.unlock();}}}
测试类:
MyLock x = new MyLock("锁X");MyLock y = new MyLock("锁y");Executor exec = new Executor();ThreadA a = new ThreadA("线程A",x,y,exec);ThreadB b = new ThreadB("线程B",x,y,exec);a.start();b.start();
这段程序运行的结果就会造成死锁,下面,将Executor的work()方法改用轮询锁来解决死锁问题
轮询锁是通过tryLock()方法来实现的,这个方法尝试获取一个锁,如果获取到,返回true,否则返回false,这种立即返回的方式,避免了无效的等待。当嵌套调用锁时,如果里层的tryLock()方法返回false,就释放外层的锁,以便让其他线程获得所有的锁
使用轮询锁需要注意的一个重点是:各线程不要使用相同时间间隔来轮询锁,否则很容易发生活锁,如下面的例子
while(true){String threadName = Thread.currentThread().getName();if(a.lock.tryLock()){System.out.println(threadName+"获得"+a.name+"的锁");try{TimeUnit.SECONDS.sleep(1);System.out.println(threadName+"请求"+b.name+"的锁");if(b.lock.tryLock()){try{System.out.println(threadName+"获得"+b.name+"的锁");return;}catch(Exception e){//...}finally{b.lock.unlock();}}}catch(Exception e){//...}finally{a.lock.unlock();}}}
发生活锁的原因是:
线程A得到锁X请求锁Y,线程B得到锁Y请求锁X,此时两个线程都不得到内层锁而放弃外层锁,然后又立即做相同的步骤,这样,活锁就发生了
我们只需要在每次释放外层锁之后,轮询之前等待一个随机时间,就能很大程度避免发生活锁
3、定时锁
在使用tryLock()方法时,可以加入参数,指定超时时间,在这个时间内如果等待不到锁,则会返回false
4、可中断锁
在等待获取内置锁的情况下是不能响应中断的,这让实现可取消的任务变得复杂。
Lock实现了可中断锁,能够响应中断:lock.lockInterruptibli();——获取一个可响应中断的锁
5、公平锁和非公平锁
ReentrantLock的构造函数中提供了公平锁(默认)和非公平锁的选择
公平锁:所有请求锁的线程在队列中等待锁,线程获得锁的顺序按先来先得的原则
非公平锁:当线程请求的锁不能得到时,就在队列中等待,如果线程请求锁时,该锁刚好被释放,那么该线程可以得到这个锁
一般情况下,非公平锁的性能要高于公平锁。因为公平性会造成较多的线程挂起和恢复操作,在恢复一个被挂起的线程与该线程真正运行之间存在严重的延迟,比如:在高并发的情况下,当线程A是否一个锁时,因等待锁而被挂起的线程B会被唤醒,在B被完全唤醒之前,其他线程可能已经完成了"获得-释放"该锁的动作,而并没有影响B获得锁的时刻,如果采用公平锁,其他线程无法请求这个锁,吞吐率就降低了
因此:如果线程持有锁的时间很短,应该尽量使用非公平锁
6、读写锁
互斥锁是最保守的加锁策略,在很多情况下并不需要如此严格的加锁方式,并发的读操作如果使用互斥锁是不必要的,而且会降低读的性能,我们只要保证不会读取到脏数据即可,即:读取数据时不允许写,但允许读。读写锁可以实现这个功能,看下面的例子:
private ReadWriteLock lock = new ReentrantReadWriteLock();private Lock readLock = lock.readLock();private Lock writeLock = lock.writeLock();public void read(){readLock.lock();try {//...} catch (Exception e) {}finally{readLock.unlock();}}public void write(){writeLock.lock();try {//...} catch (Exception e) {}finally{writeLock.unlock();}}
线程A持有读锁,线程B请求读锁,线程B可以立即获得读锁
线程A持有读锁,线程B请求写锁:线程B需要在线程A释放读锁后才能获得写锁
线程A持有写锁,线程B请求读锁:线程B需要在线程A释放写锁后才能获得读锁
线程A持有写锁,线程B请求写锁:线程B需要在线程A释放写锁后才能获得写锁
读、写锁之间可以定义不同的交互方式,包括:
1、当释放写锁时,读线程和写线程哪个优先得到锁
2、当释放读锁时,并且队列中有写线程正在等待,此时是否允许读线程插队
3、读、写锁是否允许重入
4、锁降级:线程将持有的写锁在不释放锁的情况下转变为读锁
5、锁升级:线程将持有的读锁在不释放锁的情况下转变为写锁
ReentrantReadWriteLock的公平锁情况下:如果一个线程持有读锁,当等待线程中有请求写锁时,优先于等待的读锁;锁降级是允许的,但锁升级不允许,这样会导致死锁,因为写锁是互斥的,获得写锁要等待所有其他线程释放读写锁,如果两个线程同时升级锁,那么会同时在等待对方释放锁
读写锁的性能
在读操作较多的情况下,读写锁能提高性能,其他情况下要比独占锁的性能差,因为它的实现比较复杂
总结:
Lock提供一种灵活的加锁机制,在很多场合,是不适合使用同步代码块的,同步代码块的缺点和优点集于一身:锁的获取和释放是在同一个代码块中的,好处是我们不必考虑代码块中遇到异常退出时需要如何释放锁,缺点是无法实现连锁式加锁
连锁式加锁是指:在锁分段的应用场景下,比如ConcurrentHashMap,它把散列桶分成不同的段,每个段使用一个独立的锁,当我们需要遍历时,需要持有某个段的锁,直到获得下一个段的锁才释放原有的锁,这种情况下,内置锁是无法完成的
关于synchronized和ReentrantLock的选择
在Java 5.0时代,ReentrantLock的性能比内置锁有更好的竞争性能,而Java 6之后改进了内置锁,二者性能相差无几
由于ReentrantLock存在更高的活跃性危险,因此一般情况下优先使用synchronized,只有需要使用ReentrantLock的特殊功能是才考虑ReentrantLock
并且,在后面的Java版本中可能会提升synchronized而不是ReentrantLock的性能,因为synchronized是JVM的内置属性,可以执行一些优化
- 读书笔记:Java并发实战 第13章 显式锁
- java并发编程实战-读书笔记-第1章 简介
- java并发实战读书笔记
- 【Java并发】JAVA并发编程实战-读书笔记13
- 读书笔记:Java并发实战第15章 原子变量与非阻塞同步机制
- 《Java并发编程实战》读书笔记
- 《Java并发编程实战》读书笔记
- java并发编程实战-读书笔记
- 《Java并发编程实战》读书笔记
- 《java并发编程实战》读书笔记
- 《Java并发编程实战》读书笔记
- 读书笔记-《Java并发编程实战》
- java并发编程实战读书笔记
- java并发编程实战读书笔记
- Java并发编程实战读书笔记
- effective java 读书笔记---第10章 并发
- java并发编程实战第7章
- java并发编程实战第8章
- DOM事件探秘之一
- 杂谈
- vbscript常用运算符与函数
- 【Western Digital】西数电路板安全模式
- Html5旋转绘画大法
- 读书笔记:Java并发实战 第13章 显式锁
- cell自动计算高度(masonry与UITableView-FDTemplateLayoutCell)
- 获取发布时间距离当前时间的时间
- python学习笔记(一)---python简介
- qtp vb正则表达式RegExp、随机函数参数化RandomNumber实现
- 转的,备忘下,Arraylist排序
- LeetCode Search in Rotated Sorted Array
- log4j+flume+kafka+strom整合
- An error occured starting Mathtype's Commonds for word(office32+mathType6.8)