库存-并发学习笔记 (7)

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 在并发编程领域走的很快,
重点支持的还是管程模型。 管程模型理论上解决了信号量模型的一些不足,主要体现在易用性和工程化方面,
例如用信号量解决我们曾经提到过的阻塞队列问题,就比管程模型麻烦很多。

17 | ReadWriteLock:如何快速实现一个完备的缓存?

笔记

管程和信号量都能解决所有并发问题了,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

无锁方案的优点

无锁方案相对于互斥锁方案,最大的好处就是性能

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygwfg.html