true:公平,先来后到
public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }总结
信号量在 Java 语言里面名气并不算大,但是在其他语言里却是很有知名度的。Java 在并发编程领域走的很快,
重点支持的还是管程模型。 管程模型理论上解决了信号量模型的一些不足,主要体现在易用性和工程化方面,
例如用信号量解决我们曾经提到过的阻塞队列问题,就比管程模型麻烦很多。
笔记
管程和信号量都能解决所有并发问题了,JUC中还存在那么多并发工具?
分场景优化,提升易用性
什么是读写锁
读写锁普遍存在于各种语言,三条基本原则
允许多个线程同时读共享变量
统一时刻只允许一个线程写共享变量
如果一个写线程正在执行写操作,此时禁止读线程读共享变量(读锁和写锁是互斥的)
对比mysql(以下两条结论都建立在两个独立的事务中)
在read-uncommitted、read-committed、repeatable-read级别下,对同一行数据的写不会阻塞读。原因在于在以上三个隔离级别中,是通过MVCC控制的。当然如果采用当前读(lock in share mode读读不互斥,读写互斥。ps:for update读读互斥、读写互斥)则可以产生阻塞
在serializable隔离级别下,同读写锁的第三条读写互斥规则。原因是在serializable级别下所有的读都是当前读(互斥读)。
mysql 演示
-- session1 -- 查询事务级别 select @@tx_isolation; -- 设置事务级别 set session transaction isolation level read committed; -- 开启事务 start transaction; select * from sys_test where id = 2 ; -- 加上lock in share mode同读写锁的读写互斥; -- 提交事务 commit; -- session2 set session transaction isolation level read committed; start transaction; update sys_test set name = '55' where id = 2; select * from sys_test where id = 2; commit;ReadWriteLock读写锁
读多写少的场景
缓存
读写锁的升级与降级
ReadWriteLock不支持锁升级(会饥饿),但是支持锁降级。
理解
ReadWriteLock读写锁如果不互斥,也就没必要存在读锁了。
类似mysql,如果读写不互斥,则没必要加读锁。
读锁存在的意义在于第三条规则。写同时阻塞读,可以保证读到的一定是最新的。
mysql在前三个隔离级别下默认读是快照读(无锁读),所以才存在了脏读、不可重复度、幻读。所以解决以上三个问题的终极方法就是所有读都采用当前读(锁读)、当然这会影响性能,不建议使用。
18 | StampedLock:有没有比读写锁更快的锁?笔记
StampedLock 和 ReadWriteLock的区别
ReadWriteLock 支持两种模式(读读不互斥,写写互斥,读写互斥)
读锁
写锁
StampedLock 支持三种模式
写锁 语义同ReadWriteLock的写锁
悲观读锁 语义同ReadWriteLock的读锁
乐观读锁 乐观锁-无锁,性能更好
StampedLock 锁升级 (不是内部实现)
当 StampedLock 乐观读期间遇到写操作(validate(stamp)方法可判断)
注意事项
StampedLock 是不可重入的
使用 StampedLock 一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()。这个规则一定要记清楚。
范式
读范式final StampedLock sl = new StampedLock(); // 乐观读 long stamp = sl.tryOptimisticRead(); // 读入方法局部变量 ...... // 校验stamp if (!sl.validate(stamp)){ // 升级为悲观读锁 stamp = sl.readLock(); try { // 读入方法局部变量 ..... } finally { //释放悲观读锁 sl.unlockRead(stamp); } } //使用方法局部变量执行业务操作 ......
写范式long stamp = sl.writeLock(); try { // 写共享变量 ...... } finally { sl.unlockWrite(stamp); }
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?笔记
CountDownLatch
主要用来解决一个线程等待多个线程的场景。可以类比旅游团团长要等待所有的游客到齐才能去下一个景点;
一旦计数器到0,再有线程调用await(),会直接通过。
CyclicBarrier
一组线程之间互相等待。
具备自动重置功能。CyclicBarrier 的计数器是可以循环利用的。一旦计数器减到 0 会自动重置到你设置的初始值。
CyclicBarrier 还可以设置回调函数
TODO
CyclicBarrier实操
20 | 并发容器:都有哪些“坑”需要我们填?笔记
List
LinkedList
ArrayList
同步容器
Vector
并发容器
CopyOnWriteArrayList
Set
HashSet
TreeSet
LinkedSet
并发容器
CopyOnWriteArraySet
CopyOnWriteSkipListSet
Map
LinkedHashMap
HashMap
TreeMap
同步容器
HashTable
并发容器
ConcurrentHashMap
ConcurrentSkipListMap
Queue
非阻塞
线程不安全
PriorityQueue
LinkedList
线程安全
单端
ConcurrentLinkedQueue
双端
ConcurrentLinkedDeque
阻塞
ArrayBlockingQueue
出队入队同一把锁
底层数据结构:数组
有界
默认不保证线程安全性
LinkedBlockingQueue
底层链表
“有界”阻塞队列(长度为int长度)
SynchronousQueue
无空间(不存储元素)
LinkedTransferQueue
无界:由链表组成的无界TransferQueue
PriorityBlockingQueue
支持优先级
无界
DelayQueue
延时阻塞队列
无界
对于Collections.synchronizedXXX()的方法要着重注意竞态条件问题
使用无界队列时要着重注意oom问题。例如:线程池的阻塞队列
21 | 原子类:无锁工具类的典范笔记
CAS全程
Compare And Swap
无锁方案的优点
无锁方案相对于互斥锁方案,最大的好处就是性能