synchronized 使用及底层原理详解

2021-01-20  本文已影响0人  keyuan0214

转载来源于:https://blog.csdn.net/u012988901/article/details/112006772。后续的几个并发编程相关的文章也来自这位老哥。写的博客很不错。条理清晰,表达顺畅,通俗易懂。

一、线程安全问题

1、临界资源

多线程编程中,有可能会出现多个线程同时访问同一个共享、可变资源的情况,这个资源我们称之其为临界资源;这种资源可能是:对象、变量、文件等。

2、线程安全问题

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的,否则就是非线程安全的。

3、如何解决线程安全问题

互斥同步(Mutual Exclusion & Synchronization)是一种最常见也是最主要的并发正确性保障手段。同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条(或者是一些,当使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是常见的互斥实现方式。

在Java里面,最基本的互斥同步手段就是synchronized关键字,另外还有从JDK1.5开始引入了JUC里面的Lock接口,其中用的比较多的就是ReentrantLock,后面也会进行介绍。

二、synchronized使用介绍

synchronized是JVM内置的,是可重入的,其使用方法有三种:加在static修饰的静态方法上,加在普通方法上,同步代码块三种方式。

从上面可以看出synchronized锁的其实都是对象。

三、synchronized实现原理

1、synchronized底层指令:monitorenter和monitorexit

synchronized是基于JVM内置锁实现,通过内部对象Object Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低。当然,JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,内置锁的并发性能已经基本与Lock持平。

注意:只有synchronized锁升级为重量级锁时才会用到Object Monitor(监视器锁)。

synchronized关键字被编译成字节码后会被翻译成monitorenter 和monitorexit 两条指令分别在同步块逻辑代码的起始位置与结束位置。

public class TestSynchronized {    private Object obj = new Object();    public void testLock() {        synchronized (obj) {            System.out.println("获取了锁");        }    }}

我们通过javap -c TestSynchronized.class将上面代码的class文件进行反汇编,可以看到如下所示:我们看到了monitorenter 和monitorexit 两条指令,但是monitorexit却出现了两次,原因如下:

public void testLock();    Code:       0: aload_0       1: getfield      #3                  // Field obj:Ljava/lang/Object;       4: dup       5: astore_1       6: monitorenter       7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;      10: ldc           #5                  // String 鑾峰彇浜嗛攣      12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V      15: aload_1      16: monitorexit      17: goto          25      20: astore_2      21: aload_1      22: monitorexit      23: aload_2      24: athrow      25: return    Exception table:       from    to  target type           7    17    20   any          20    23    20   any

2、Object Monitor(监视器锁)机制

上面提到了,只有synchronized锁升级为重量级锁时才会用到Object Monitor(监视器锁)。我们看一下Object Monitor的实现机制是什么?查看OpenJDK源码可以看到Object Monitor由C++语言实现,打开JDK源码目录 “jdk\hotspot\src\share\vm\runtime“可以看到objectMonitor.hpp,这个就是监视器锁的实现,截取一段代码如下:

ObjectMonitor() {   _header       = NULL; //对象头 _count        = 0;  //记录加锁次数,锁重入时用到 _waiters      = 0, //当前有多少处于wait状态的thread   _recursions   = 0; //记录锁的重入次数   _object       = NULL;   _owner        = 0; //指向持有ObjectMonitor对象的线程 _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet   _WaitSetLock  = 0 ; _Responsible  = NULL ;  _succ         = NULL ;  _cxq          = NULL ;  FreeNext      = NULL ;  _EntryList    = NULL ;//处于等待加锁block状态的线程,会被加入到该列表   _SpinFreq     = 0 ; _SpinClock    = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0;}

其中几个比较重要的字段:

ObjectMonitor的加锁解锁过程如下图所示,ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装成ObjectWaiter对象);整个monitor运行的机制过程如下:

image
上一篇 下一篇

猜你喜欢

热点阅读