这个月忙着期末考试还有项目对接的事情,忙着忙着就没怎么更新博客了😭
今天请假考试顺便整理下几篇大佬的博客,便于后面复习。
1.Synchronized锁实现原理:
在字节码层面上看,这种加锁方式会在同步块的前后分别加上monitorenter和monitorexit这个两个字节码指令。
(这一段取自文章https://blog.csdn.net/qq_40551367/article/details/89414446。加粗部分为个人补充)
monitorenter :
每个对象有一个监视器锁(monitor)(-或者说每个对象会跟一个监视器绑定,加锁释放锁的操作其实是对这个监视器进行操作的)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit:
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
(在这里我从操作系统上的管程去理解,monitorenter至monitorexit这一块交由管程控制,同一时间只能有一个进程可以进入到管程中。)
2.ReentrantLock锁实现原理:
CAS+CLH队列来实现。它支持公平锁和非公平锁,两者的实现类似。
在这里补充一下下,Synchronized锁是非公平锁,ReentrantLock锁通过构建时传入的参数可以设置为公平锁(默认是非公平锁)。公平锁和非公平锁的概念以及性能在文章最后记录下。
CAS:Compare and Swap,比较并交换。CAS有3个操作数:内存值V、预期值A、要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。该操作是一个原子操作,被广泛的应用在Java的底层实现中。在Java中,CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现。
CLH队列:带头结点的双向非循环链表
AbstractQueuedSynchronizer:
ReentrantLock实现的前提就是AbstractQueuedSynchronizer,简称AQS,是java.util.concurrent的核心,CountDownLatch、FutureTask、Semaphore、ReentrantLock等都有一个内部类是这个抽象类的子类。
由于ReentrantLock我们用的比较多的是非公平锁,所以看下非公平锁是如何实现的。假设线程1调用了ReentrantLock的lock()方法,那么线程1将会独占锁,整个调用链十分简单:
第一个获取锁的线程就做了两件事情:
1、设置AbstractQueuedSynchronizer的state为1
2、设置AbstractOwnableSynchronizer的thread为当前线程
3.公平锁和非公平锁:
参考以下文章:
https://www.jianshu.com/p/f584799f1c77
个人理解两者区分在于获取锁的进程有没有可能被插队,有可能被插队那就是那就不公平啦。下面是大佬的神图解:
1、若在释放锁的时候总是没有新的兔子来打扰,则非公平锁等于公平锁;
2、若释放锁的时候,正好一个兔子来喝水,而此时位于队列头的兔子还没有被唤醒(因为线程上下文切换是需要不少开销的),此时后来的兔子则优先获得锁,成功打破公平,成为非公平锁;
而非公平锁效率高于公平锁的原因,就在于上文说到的线程切换的开销。因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。
再次贴出参考文章:
https://www.jianshu.com/p/f584799f1c77
https://blog.csdn.net/javazejian/article/details/72828483
https://blog.csdn.net/qq_40551367/article/details/89414446