CAS
CAS Compare and Swap一种高效实现线程安全性的方法
支持原子更新操作,适用于计数器,序列发生器(给变量自增)等场景。
属于乐观锁机制,号称lock-free。
CAS操作失败时由开发者决定是继续尝试,还是执行别的操作。
CAS思想
包含三个操作数——内存位置(V)、预期值(A)和新值(B)
执行时将内存位置的值与预期值比较,相等则改为新值,不同则不做操作
CAS多数情况下对开发者来说是透明的
J.U.C的atomic包提供了常用的原子性数据类型以及引用、数组等相关原子类型和更新操作工具,是很多线程安全程序等首选。
Unsafe类虽然提供CAS服务,但因能够操纵任意内存地址读写而有隐患
Java 9 以后,可以使用Variable Handle API来替代Unsafe
CAS缺点
若循环时间长 ,则开销很大
只能保证一个共享变量的原子操作
ABA问题
如果内存地址V初次读取到的值是A,在准备改变时发现仍然是A,就默认其没被改变过,但实际上可能被改成A又被改成B。
解决:AtomicStampedRefernce,带有标记的原子引用类。通过控制变量值的版本来保证 ...
通过redis实现分布式锁
实现分布式锁分布式锁需要解决的问题
互斥性
任一时刻,只能有一个客户端获取锁
安全性
锁只能由持有的客户端删除
死锁
避免某些客户端因宕机等原因未能释放锁,而其他客户端再也无法获取该锁
容错
当部分节点宕机后客户端应仍然能够获取锁和释放锁
如何通过Redis实现分布式锁方案一(不推荐)SETNX key value :如果key不存在,创建并赋值
时间复杂度O(1)
返回值:key不存在,设置成功,返回1,key存在则失败返回0
因为setnx是原子的又有以上特性,初期被用来实现分布式锁
执行某段代码时先尝试使用SETNX对某个key设值,如果设置成功,则证明没有别的线程在执行该段代码。
如何解决SETNX长期有效的问题?EXPIRE key secons
设置key的生存时间,当key过期时会被自动删除
此方法的问题如果一个线程设置了key还没来得及设置过期时间就宕机了,就会发生死锁
主要原因就是原子性不能得到满足
方案二将SETNX和EXPIRE放在一块执行
SET key value [EX seconds] [PX milliseconds] [NX|XX ...
使用Redis做异步队列
使用Redis做异步队列使用List作为队列,RPUSH生产消息,LPOP消费消息缺点一:
没有等待队列里有值就直接消费
第一种弥补方案:
可以通过在应用层引入Sleep机制去调用LPOP重试
第二种弥补方案:使用命令:
BLPOP key [key...] timeout
阻塞直到队列有消息或者超时
缺点二:
只能供一个消费者消费
弥补:pup/sub:主题订阅者模式
发送者(pup)发送消息,订阅者(sub)接收消息。
订阅者可以订阅任意数量的频道
订阅频道: subscribe channel [channel...]发布消息:PUBLIC channel Message此方法的缺点:
消息的发布是无状态的,不能保证可达
要解决此问题只能使用专业的消息队列如kafaka等