https://www.cnblogs.com/jinshuai86/p/9226164.html
Java编程的逻辑
Java并发编程的艺术
极客时间:Java并发编程实战
并发工具1:同步协作工具
- 读写锁ReentrantReadWriteLock
- 信 量Semaphore
- 倒计时门栓CountDownLatch
- 循环栅栏CyclicBarrier
- 交换者Exchanger
基于AQS实现
在一些特定的同步协作场景中,相比使用最基本的wait/notify、显示锁/条件,它们更为方便,效率更高
1 读写锁ReentrantReadWriteLock
1.1 场景
synchronized和ReentrantLock对于同一受保护对象的访问,无论是读还是写,它们都要求获得相同的锁
在一些场景中,这是没有必要的,多个线程的读操作完全可以并行;
在读多写少的场景中,让读操作并行可以明显提高性能
使用读写锁可以让读操作能够并行,又不影响一致性
使用读写锁+hashmap,可实现线程安全的缓存
1.2 概念
接口ReadWriteLock表示读写锁,主要实现类是可重入读写锁ReentrantReadWriteLock
ReadWriteLock接口如下:
ReentrantReadWriteLock是可重入的读写锁,它有两个构造方法:
1.3 实现原理
- 读写锁的实现与协调
维护了一对锁:一个读锁和一个写锁
内部,它们使用同一个整数变量表示锁的状态,16位给读锁用(高16位),16位给写锁用(低16位),使用一个变量便于进行CAS操作,锁的等待队列其实也只有一个。
写锁的获取,就是确保当前没有其他线程持有任何锁,否则就等待。
写锁的释放,也就是将等待队列中的第一个线程唤醒,唤醒的可能是等待读锁的,也可能是等待写锁的。
读锁的获取则不太一样:
首先,只要写锁没有被持有,就可以获取到读锁,此外,在获取到读锁后,它会检查等待队列,逐个唤醒最前面的等待读锁的线程,直到第一个等待写锁的线程。
如果有其他线程持有写锁,获取读锁会等待。
读锁释放后,检查读锁和写锁数是否都变为了0,如果是,唤醒等待队列中的下一个线程
- 读写锁确定读和写各自的状态的方法
通过位运算
假设当前同步状态值为S,写状态等于S&0x0000FFFF(将高16位全部抹去),读状态等于S>>>16(无符 补0右移16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是
S+0x00010000
- 锁降级
锁降级指的是写锁降级成为读锁。
如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。
锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
案例代码:
若当前线程不获取读锁而是直接释放写锁:
假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新,会使用先前写锁准备的数据,而不是T修改后的数据;
若当前线程获取读锁,即遵循锁降级的步骤:
线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新,当前线程可以感知线程T的数据更新。
不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程),也是为了保证数据可见性:
如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其他获取到读锁的线程是不可见的。
2 信 量Semaphore
2.1 场景
Semaphore用来限制对资源的并发访问数
1.有的单个资源即使可以被并发访问,但并发访问数多了可能影响性能,所以希望限制并发访问的线程数。
2.与软件的授权和计费有关,对不同等级的账户,限制不同的最大并发访问数;
如限制并发访问的用户数不超过100:
3.资源往往有多个,但每个同时只能被一个线程访问,比如,饭店的饭桌、火车上的卫生间、食堂排队打饭(https://www.cnblogs.com/pony1223/p/9479299.html)
2.2 概念
每一次的acquire调用都会消耗一个许可
一般锁只能由持有锁的线程释放,而Semaphore表示的只是一个许可数,任意线程都可以调用其release方法
方法:
2.3 实现原理
构造方法的参数permits表示共享的锁个数;
acquire方法:检查锁个数是否大于0,大于则减一,获取成功,否则就等待;(即down())
release方法:将锁个数加一,唤醒第一个等待的线程(即up())
模型:
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!