标签:垃圾 中间 monit 决定 初始化 操作 hashmap abstract invoke
public class SyncTest {
Object lock = new Object();
public void sync(){
synchronized (lock) {
System.out.println("get lock");
}
}
}
很简单,我们先new了一个锁,当多个线程需要去调用sync方法然后输出信息的之前,需要这个锁打开才能输出,这是一段典型的同步代码块代码,前面说了,synchronized是利用jvm自带的指令集来实现锁的功能的,那我们现在就利用java自带的反编译工具(javap
-v SyncTest.class),把指令集输出来,我们重点分析这个sync方法:
这个就是反编译class文件的sync方法的结果,下面我们一行一行来分析:
descriptor:()V,这行是方法的说明,表示这个方法没有入参,返回类型是void
flags:ACC_PUBLIC,说明是public方法
code:说明下面是方法代码区域
stack=2,locals=3,args_size=1,表示这个执行这个方法虚拟机栈深度只需要2,本地变 量有3个,有1个参数,1个参数就是this(java方法的第一个参数都是this,只不过隐藏掉了)
aload_0:装载第一个局部变量到操作栈,这里就是this
getfield #3 :#3指向常量池第三个位置,我没有贴出常量池的反编译视图,在这里就是代 码中lock对象,整行指令意思就是访问这个lock对象的引用
dup:复制上面getfield获取的引用压入栈,这里就是lock的引用
astore_1:弹出栈顶的引用,然后放入局部变量1的位置中
monitorenter:得到lock对象的monitor,monitor的进入计数count+1,这个指令就是我们
Synchronized在jvm的底层实现,线程在打印之前需要得到lock的monitor,如果获取不 到,则被挂起,获取到了就继续执行,在monitorenter下面,还有操作系统级别的 mutex重量级锁以及jvm利用cas优化的jvm级的轻量锁,这个不在我们这次讨论范围。
getstatic #4:访问静态变量System.out:PrintStream
ldc #5:将常量池第5个常量“get lock”压入栈
invokevirtual #6:执行数据输出函数
aload_1:装载第二个局部变量,这里就是astore_1从栈上弹出的lock对象的引用
monitorexit:monitor的计数-1,因为是可重入的,就是一个线程可以多次拿到monitor, 所以当monitor计数为0的时候释放lock锁。
goto 25:看一下25行是return,就是返回,如果代码正常执行,那么久流程就结束了,如 果代码中间出现了异常,则继续走
astore_2:弹出栈顶引用,放到局部变量2
aload_1:装载第二个局部变量,这里就是astore_1从栈上弹出的lock对象的引用
monitorexit:monitor的计数-1,出现异常也释放掉锁。
aload_2:装载第三个局部变量,这里就是astore_2从栈顶弹出来的引用。
athrow:将栈顶的数据作为异常抛出,如果为null,则抛空指针异常
return:结束本方法调用的栈帧
下面的异常表,调试行信息和栈图就不解释了,我也不是很懂,以免误导,想深入研究可以找资料学习
到这里,这个方法在jvm指令层面的执行顺序就结束了,java 关键字Synchronized就是通 过monitorenter,monitorexit两个指令来实现的,在monitor下面还有jvm通过cas优化过2的轻量锁以及操作系统级别的重量互斥锁mutex,这个本期不做讨论。
reentrantlock:这个锁在java层面就是一个类,不像上面的Synchronized是一个关键字他是doug lea大神在 java1.5中主导的因为解决那个时候Synchronized效率低下的出现的,这个类可以有很多东西讲,在这里我抽重点讲,首先在java层面,reentrantlock以及其他同步工具类,比如ConcurrentHashMap(java 1.8改动很大,没仔细看源码,好像和以前不太一致了,1.8以前是),CountDownLatch,CyclicBarrier,以及信号量 semaphore,在底层都是通过实现抽象类AbstractQueuedSynchronizer(AQS)来实现各种锁功能,比如乐观锁,悲观锁,互斥锁,共享锁,读写锁等。先来说一下reentrantlock的lock实现原理。
reentrantlock.lock:reentrantlock类自己实现了AQS,内部有两种实现方式,有公平实现 类FairSync和不公平实现类NonfairSync,这里我们说默认的使用不公平sync:
lock方法首先会用CAS采用乐观锁方式获取一次锁,(CAS操作线程安全,因为通过jni直接调用的操作系统cmpxchg指令,(如果是多核CPU,则需要调用lock cmpxchg利用内存屏障来保障指令的原子性)如果成功了,则设置当前线程占有这个锁,如果失败了,则去acquire(1),意思是提交一个获取锁的申请:
首先tryAcquire(1),调用到NonfairSync的nonfairTryAcquire方法:
首先获取一下state,如果是0则说明还没占有锁,则再去CAS获取一次,意图是代码从外面的CAS走到这的时间里,可能别的线程已经释放了锁,所以在进行一次CAS,如果获取到了则返回成功。如果state不是0,且占有锁的线程是当前线程,因为是可重入的锁,则在计数上加1,代表重入一次,等到时候释放锁的时候,要全部释放完等计数为0才表示释放完全。
如果都不是以上情况,则返回false,进行后面的操作:
new一个当前线程的node,然后通过CAS操作,把这个node排在node双向链表的尾部,如果CAS失败,则进行enq:
enq很简单,一个死循环既自旋,如果链表是空的,则初始化,否则一直CAS到node排到链表尾部为止。添加等待node成功之后,执行以下方法:
进行循环,Node p就是当前线程node前面的一个node,如果前一个node就是链表头结点以及马上进行一次CAS,如果成功获取到锁了,那么把当前线程的node设置成head,且断开当前node和前node的链,返回fase,表示获取到锁。如果不是,则进行下一步:
这个方法的目的是返回当前线程是否应该被阻塞,pred node就是当前线程node的前面一个等待node,注释说的很清楚,如果前面的node状态是signal,表示当前线程可以安全的park住,等待被唤醒,如果WS>0就是取消状态,如果前置node是取消状态,则双向链表往前回溯,直到找到当前node的前置node的状态不是取消状态,然后把当前阶段的前置node指向这个节点,如果是其他状态,则要把前置阶段设置成signal状态,等下次循环到这个方法的时候,就可以判断出这个节点是需要park的,当判断需要阻塞线程之后,执行:
这个方法就是调用了unsafe类的park方法,unsafe通过JNI调用了本地的C++实现的mutex锁,这个也不做展开。当这个线程被unpark唤醒的时候,返回的线程的中断状态。到这里,reentrantlock的非公平锁的原理就说完了,公平锁的原理比这个还要简单一些,想知道原理的可以自行查阅资料研究。说到底,reentrantlock以及其他的concurrent包下的同步类,在java层面都是利用集成了AQS的子类,去实现锁,怎样锁则是通过unsafe类调用了C++代码实现的mutex互斥锁。释放锁unlock操作,则是通过释放当前线程的充入数量,然后通过之前的双向链表,通过unpark方法去唤醒next node,当next node被唤醒之后,怎会继续执行
这个代码块,如果这个时候没有其他的线程去抢占这个锁,那么tryAcquire操作就会成功占有锁,如果这个时候有另外一个线程抢先CAS成功了,则这线程继续阻塞,因为这个是非公平锁。
到这里,锁就java的两个锁就说完了,总结一下,synchronized关键字是通过对象的monitor,然后通过monitorenter和monitorexit jvm指令来完成的。而Doug lea大神编写的concurrent包的同步类,在java层面通过继承AQS,然后配合unsafe提供的CAS操作以及链表这个数据结构,去实现乐观锁,互斥,共享,自旋等锁,当需要阻塞线程的时候,通过unsafe类调用操作系统的mutex互斥锁去实现阻塞。
总体已经说完了,java多线程并发涉及的知识点如果要深挖其实还有非常多,如果要一篇说全,包含了太多东西,java内存结构,jvm虚拟机,指令集,第一没这么多时间,很多大神用一本书都说不完,第二,本人知识储备还不够,没掌握的东西就不说了,以免误导,这边文章就介绍java的两种同步机制,synchronized以及reentrantlock。
如有不对的地方,希望可以及时指正,后面我会抽时间再来讲一下java的内存模型,内存分区,垃圾回收机制,以及多线程,线程池等相关的知识点,用来记忆和巩固,28岁了,有些东西不常用的话会忘。。。
java两种同步机制的实现 synchronized和reentrantlock
标签:垃圾 中间 monit 决定 初始化 操作 hashmap abstract invoke
原文地址:http://www.cnblogs.com/diegodu/p/7998337.html