专栏名称: java那些事
分享java开发中常用的技术,分享软件开发中各种新技术的应用方法。每天推送java技术相关或者互联网相关文章。关注“java那些事”,让自己做一个潮流的java技术人!《java程序员由笨鸟到菜鸟》系列文章火热更新中。
目录
相关文章推荐
51好读  ›  专栏  ›  java那些事

看了都说好:深入理解 Java 锁与线程阻塞,才是你的最佳选择

java那些事  · 公众号  · Java  · 2019-04-23 16:00

正文

请到「今天看啥」查看全文



可以看到方法表的访问标志位 (flags) 中多了个 ACC_SYNCHRONIZED,然后看字节码指令区域 (Code) ,和普通方法没任何差别, 猜测 Java 虚拟机通过检查方法表中是否存在标志位 ACC_SYNCHRONIZED 来决定是否需要获取锁,至于获取锁的原理后文会提到。


然后看第二个使用 synchronized 区块的方法(Lock.print2)字节码:


public static void print2 () ;
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack= 2 , locals= 2 , args_size= 0
0 : ldc # 5 // 将锁对象 Lock.class 入栈
2 : dup // 复制一份,此时栈中有两个 Lock.class
3 : astore_0 // 出栈一个 Lock.class 对象保存到局部变量表 Slot 1 中
4 : monitorenter // 以栈顶元素 Lock.class 作为锁,开始同步
5 : getstatic # 2 // 5-10 调用 System.out.println("synchronized");
8 : ldc # 6
10 : invokevirtual # 4
13 : aload_0 // 将局部变量表 Slot 1 中的数据入栈,即 Lock.class
14 : monitorexit // 使用栈顶数据退出同步
15 : goto 23 // 方法结束,跳转到 23 返回
18 : astore_1 // 从这里开始是异常路径,将异常信息保存至局部变量表 Slot 2 中,查看异常表
19 : aload_0 // 将局部变量表 Slot 1 中的 Lock.class 入栈
20 : monitorexit // 使用栈顶数据退出同步
21 : aload_1 // 将局部变量表 Slot 2 中的异常信息入栈
22 : athrow // 把异常对象重新抛出给方法的调用者
23 : return // 方法正常返回
Exception table: // 异常表
from    to  target type
5 15 18 any // 5-15 出现任何(any)异常跳转到 18
18 21 18 any // 18-21 出现任何(any)异常跳转到 18


synchronized 区块的字节码相比较 synchronized 方法复杂了许多。每一行字节码的含义我都作了详细注释,可以看到此时是通过字节码指令 monitorenter,monitorexit 来进入和退出同步的。特别值得注意的是,我们并没有写 try.catch 捕获异常,但是字节码指令中存在异常处理的代码,其实为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。这个机制确保在 synchronized 区块中产生任何异常都可以正常退出同步,释放锁资源。


不管是检查标志位中的 ACC_SYNCHRONIZED,还是字节码指令 monitorenter,monitorexit,锁机制的实现最终肯定存在于 JVM 中,后面我们会再提到这点。


接下来继续看 ReentrantLock 的实现,鉴于篇幅有限,ReentrantLock 的原理不会讲的很详细,感兴趣的可以自行研究。ReentrantLock 是基于并发基础组件 AbstractQueuedSynchronizer 实现的,内部有一个 int 类型的 state 变量来控制同步状态,为 0 时表示无线程占用锁资源,等于 1 时表示则说明有线程占用,由于 ReentrantLock 是可重入锁,state 也可能大于 1 表示该线程有多次获取锁。AQS 内部还有一个由内部类 Node 构成的队列用来完成线程获取锁的排队。本文只是简单的介绍一下 lock 和 unLock 方法。


下面先看 ReentrantLock.lock 方法:


// ReentrantLock.java
public void lock () {
this .sync.lock();
}
// ReentrantLock.NonfairSync.class
final void lock () {
// 使用 cas 设置 state,如果设置成功表示当前无其他线程竞争锁,优先获取锁资源
if ( this .compareAndSetState( 0 , 1 )) {
// 保存当前线程由于后续重入锁的判断
this .setExclusiveOwnerThread(Thread.currentThread());
} else {
this .acquire( 1 );
}
}
// AbstractQueuedSynchronizer.java
public final void acquire ( int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 如果阻塞被中断,重新设置中断通知调用者
}
// 判断是否是重入
protected final boolean tryAcquire






请到「今天看啥」查看全文


推荐文章
点点星光  ·  年底了,送给不常联系的朋友
8 年前
金融先生MrFinance  ·  年轻时一味追求高工资,会给你带来什么?
7 年前
电子工程专辑  ·  今年夏天,硅谷发生了些什么?
7 年前
房地产采购经理人家园  ·  采购管理方法58:努力影响对方
7 年前