1、单节点的redis锁
优点:快
缺点:不安全(redis节点断电,网络不通,锁过期等等)
2、多节点redis锁,即RedLock
参考文章:https://www.cnblogs.com/baichunyu/p/11631750.html
优点:快,相对安全(上了分布式redis,解决了单点问题)
缺点:还是不完全安全。
为啥说它相对安全呢?
可以参考这篇文章:https://www.cnblogs.com/baichunyu/p/11631777.html
英文博客:How to do distributed locking
总结下:应用client1自己由于gc导致STW的话,有可能因为时间过长,导致redLock的key过期,其他应用client2获取到锁,导致不安全。gc只是其中一个原因,还包括:网络包的延迟,cpu抢占,页中断等等可能造成pause的地方,都是不安全的。
二、synchronized锁
我根据图总结了下:
1、无竞争情况下,是偏向锁。不管是单线程,还是多线程交替执行,只要没有竞争发生。
2、轻量级锁:只要出现一次竞争,升级为轻量级。并且竞争不激烈(自旋次数内都能拿到锁),就一直在轻量级锁
3、只要有一个竞争线程在自旋次数达到拿不到锁,它就把锁升级为重量级锁。
锁只能膨胀,不能缩小。
原文地址:https://blog.csdn.net/wojiao228925661/article/details/100145157
synchronized重量级锁是通过JVM的monitor来实现的,monitor是基于操作系统的mutex来实现。JVM的monitor用来实现可重入,等待队列,阻塞队列等等JVM层面的功能。monitor在JVM是c++实现,结构如下:
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
很明显里面的_owner用来实现重入功能,_WaitSet用来实现等待队列,_EntryList实现阻塞队列。
_owner:指向持有ObjectMonitor对象的线程
_WaitSet:存放处于wait状态的线程队列
_EntryList:存放处于等待锁block状态的线程队列
_recursions:锁的重入次数
_count:用来记录该线程获取锁的次数
多线程获取锁的时候,会先进入_EntryList排队,然后monitor会选择对头拿锁。然后其他线程继续在_EntryList阻塞着。等到获取线程锁执行wait(),暂时放弃锁的时候,该线程会自动进入_WaitSet等待被唤醒(没唤醒前是不参与竞争锁的),然后唤醒_EntryList其中一个线程。如果这个时候条件到达换行了_WaitSet里面的某个线程,那么它会加入到_EntryList和其他正在竞争锁的线程一起,重新竞争锁。
image.pngPS:序号4的线程,在被notify唤醒后,不应该直接进入The Owner。它应该进入阻塞队列,一同参与竞争锁。
四、CAS乐观锁,JVM是如何实现呢?
既然synchronized重量级锁是通过系统调用mutex lock来实现,它要发生用户态到内核态的切换,代价太高。那么CAS是怎么实现原子性的呢?它怎么就不需要用户态到内核态的切换?
cas不需要调用系统命令,所以无需用户态到内核态的切换。那为啥它不需要系统命令呢?因为现代cpu提供了cas原子性指令,帮助了JVM实现。那么提供了这个cas指令,就一定不需要系统调用吗?其实还有个关键特征,就是这个cpu提供的cas指令不是特权指令。如果它是特权指令,只能有操作系统来执行,那么就算有cas指令,JVM用它的话照样会发生用户态到内核态切换,代价照样高。所以,幸好cpu的cas指令不是特权指令,就像普通的load,push等等大多数指令一样,不是特权指令。
JDK提供CAS的api,具体实现是JVM的JNI来做的。JNI是c++的,它通过直接调用cpu指令来实现。如下:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *)index_oop_from_field_offset_long(p, offset);
// 调用Atomic.cpp中的cmpxchg()
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
} UNSAFE_END
没有发生系统调用,就达到了原子性了吧!
网友评论