锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)
Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我们拿Java线程(二)中的一个例子简单的实现一下和sychronized一样的效果,代码如下:
- public class LockTest {
- public static void main(String[] args) {
- final Outputter1 output = new Outputter1();
- new Thread() {
- public void run() {
- output.output("zhangsan");
- };
- }.start();
- new Thread() {
- public void run() {
- output.output("lisi");
- };
- }.start();
- }
- }
- class Outputter1 {
- private Lock lock = new ReentrantLock();// 锁对象
- public void output(String name) {
- // TODO 线程输出方法
- lock.lock();// 得到锁
- try {
- for(int i = 0; i < name.length(); i++) {
- System.out.print(name.charAt(i));
- }
- } finally {
- lock.unlock();// 释放锁
- }
- }
- }
这样就实现了和sychronized一样的同步效果,需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内。
如果说这就是Lock,那么它不能成为同步问题更完美的处理方式,下面要介绍的是读写锁(ReadWriteLock),我们会有一种需求,在对数据进行读写的时候,为了保证数据的一致性和完整性,需要读和写是互斥的,写和写是互斥的,但是读和读是不需要互斥的,这样读和读不互斥性能更高些,来看一下不考虑互斥情况的代码原型:
- public class ReadWriteLockTest {
- public static void main(String[] args) {
- final Data data = new Data();
- for (int i = 0; i < 3; i++) {
- new Thread(new Runnable() {
- public void run() {
- for (int j = 0; j < 5; j++) {
- data.set(new Random().nextInt(30));
- }
- }
- }).start();
- }
- for (int i = 0; i < 3; i++) {
- new Thread(new Runnable() {
- public void run() {
- for (int j = 0; j < 5; j++) {
- data.get();
- }
- }
- }).start();
- }
- }
- }
- class Data {
- private int data;// 共享数据
- public void set(int data) {
- System.out.println(Thread.currentThread().getName() + "准备写入数据");
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.data = data;
- System.out.println(Thread.currentThread().getName() + "写入" + this.data);
- }
- public void get() {
- System.out.println(Thread.currentThread().getName() + "准备读取数据");
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "读取" + this.data);
- }
- }
部分输出结果:
- Thread-1准备写入数据
- Thread-3准备读取数据
- Thread-2准备写入数据
- Thread-0准备写入数据
- Thread-4准备读取数据
- Thread-5准备读取数据
- Thread-2写入12
- Thread-4读取12
- Thread-5读取5
- Thread-1写入12
我们要实现写入和写入互斥,读取和写入互斥,读取和读取互斥,在set和get方法加入sychronized修饰符:
- public synchronized void set(int data) {...}
- public synchronized void get() {...}
部分输出结果:
- Thread-0准备写入数据
- Thread-0写入9
- Thread-5准备读取数据
- Thread-5读取9
- Thread-5准备读取数据
- Thread-5读取9
- Thread-5准备读取数据
- Thread-5读取9
- Thread-5准备读取数据
- Thread-5读取9
我们发现,虽然写入和写入互斥了,读取和写入也互斥了,但是读取和读取之间也互斥了,不能并发执行,效率较低,用读写锁实现代码如下:
- class Data {
- private int data;// 共享数据
- private ReadWriteLock rwl = new ReentrantReadWriteLock();
- public void set(int data) {
- rwl.writeLock().lock();// 取到写锁
- try {
- System.out.println(Thread.currentThread().getName() + "准备写入数据");
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.data = data;
- System.out.println(Thread.currentThread().getName() + "写入" + this.data);
- } finally {
- rwl.writeLock().unlock();// 释放写锁
- }
- }
- public void get() {
- rwl.readLock().lock();// 取到读锁
- try {
- System.out.println(Thread.currentThread().getName() + "准备读取数据");
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "读取" + this.data);
- } finally {
- rwl.readLock().unlock();// 释放读锁
- }
- }
- }
部分输出结果:
- Thread-4准备读取数据
- Thread-3准备读取数据
- Thread-5准备读取数据
- Thread-5读取18
- Thread-4读取18
- Thread-3读取18
- Thread-2准备写入数据
- Thread-2写入6
- Thread-2准备写入数据
- Thread-2写入10
- Thread-1准备写入数据
- Thread-1写入22
- Thread-5准备读取数据
从结果可以看出实现了我们的需求,这只是锁的基本用法,锁的机制还需要继续深入学习。
ReentrantReadWriteLock读写锁的使用
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。
读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁
线程进入读锁的前提条件:
没有其他线程的写锁,
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
到ReentrantReadWriteLock,首先要做的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系。然后就是总结这个锁机制的特性了:
(a).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。
(b).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵.
(c).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。
(d).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。
(e).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。
源码分析: http://blog.csdn.net/yuhongye111/article/details/39055531
锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)的更多相关文章
- 锁对象Lock-同步问题更完美的处理方式
Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...
- 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 百篇博客分析OpenHarmony源码 | v27.02
百篇博客系列篇.本篇为: v27.xx 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立贞 ...
- 锁对象Lock
Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题: public class LockTest { publicstaticv ...
- 构建锁与同步组件的基石AQS:深入AQS的实现原理与源码分析
Java并发包(JUC)中提供了很多并发工具,这其中,很多我们耳熟能详的并发工具,譬如ReentrangLock.Semaphore,它们的实现都用到了一个共同的基类--AbstractQueuedS ...
- Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析
原文:Redisson分布式锁学习总结:可重入锁 RedissonLock#lock 获取锁源码分析 一.RedissonLock#lock 源码分析 1.根据锁key计算出 slot,一个slot对 ...
- Java显式锁学习总结之六:Condition源码分析
概述 先来回顾一下java中的等待/通知机制 我们有时会遇到这样的场景:线程A执行到某个点的时候,因为某个条件condition不满足,需要线程A暂停:等到线程B修改了条件condition,使con ...
- Java显式锁学习总结之五:ReentrantReadWriteLock源码分析
概述 我们在介绍AbstractQueuedSynchronizer的时候介绍过,AQS支持独占式同步状态获取/释放.共享式同步状态获取/释放两种模式,对应的典型应用分别是ReentrantLock和 ...
- concurrent(六)同步辅助器CyclicBarrier & 源码分析
参考文档:Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例:https://www.cnblogs.com/skywang12345/p/3533995.html简介Cy ...
- RedissonLock分布式锁源码分析
最近碰到的一个问题,Java代码中写了一个定时器,分布式部署的时候,多台同时执行的话就会出现重复的数据,为了避免这种情况,之前是通过在配置文件里写上可以执行这段代码的IP,代码中判断如果跟这个IP相等 ...
随机推荐
- java并发编程——Excutor
概述 Excutor这个接口用的不多,但是ThreadPoolExcutor这个就用的比较多了,ThreadPoolExcutor是Excutor的一个实现.Excutor体系难点没有,大部分的关键点 ...
- File System Object(FSO对象)B
一.实例FSO获取当前路径下的文件 Sub Fsotest() Dim Fso As New FileSystemObject, Path As String, File Path = ThisWor ...
- 关于dbw 与dbm 的计算
一分贝(dB)表示单元信号强度的相对差异.其比率的基础对数为10,,如dB = 10 x Log10 (P1/P2). 基础10对数规则: Log10 (AxB) = Log10(A) + Log10 ...
- Mac os x 配置maven
安装Maven 1.压缩包apache-maven-3.3.9-bin.zip 2.解压压缩包到指定文件 3.打开终端,输入以下命令,编辑环境变量文件:bash_proflie open .bash_ ...
- OpenGL(1)-环境搭建
写在前面 工作几年,开始沉心做技术,对自己的知识进行梳理. OpenGL是由khronos组织制定并维护的规范,并不是API. OpenGL在3.2之前采用的是立即渲染模式(固定渲染管线),3.2之后 ...
- AssetBundle一些问题
AssetBundle划分过细的问题,比如每个资源都是AssetBundle. 加载IO次数过多,从而增大了硬件设备耗能和发热的压力: Unity 5.3 ~ 5.5 版本中,Android平台上在不 ...
- Swing的概述
Swing在Java中是一个包,下面就是它的基本概述:简介: Java为了方便图形界面的实现, 专门设计了类库来满足各种各样的图形元素和用户交互事件, 该类库即为抽象窗口工具箱(Abstract Wi ...
- Ubuntu下LimeSDR Mini使用说明
本文内容.开发板及配件仅限用于学校或科研院所开展科研实验! 淘宝店铺名称:开源SDR实验室 LimeSDR链接:https://item.taobao.com/item.htm?spm=a230r.1 ...
- Hyperledger Fabric chaincode 开发(疑难解答)
Q&A Q1: 使用fabric release 1.2 进行golang chaincode开发时报错: ..\..\hyperledger\fabric\vendor\github.com ...
- Django_WSGIRequest对象
WSGIRequest对象 Django在接收到http请求之后,会根据http请求携带的参数以及报文信息创建一个WSGIRequest对象,并且作为视图函数第一个参数传给视图函数.这个参数就是dja ...