目录
      • 2、object monitor(监视器锁)机制

    一、线程安全问题

    1、临界资源

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

    1. 共享:资源可以由多个线程同时访问
    2. 可变:资源可以在其生命周期内被修改

    2、线程安全问题

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

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

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

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

    二、synchronized使用介绍

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

    1. 加在静态方法上(public synchronized static void test()),锁的是当前类的class对象
    2. 加在实例方法上(public synchronized void test()),锁的是当前对象
    3. synchronized同步代码块(synchronized(object) {……}),锁的是synchronized后面括号里面的对象

    从上面可以看出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却出现了两次,原因如下:

    • 第一个monitorexit指令是同步代码块正常释放锁的一个标志;
    • 如果同步代码块中出现exception或者error,则会调用第二个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;
    }

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

    • _header 对象头,前面说过synchronized锁升级为重量级锁之后才会用到objectmonitor,这时候对象头的mark word会有一个指向重量级锁monitor的指针
    •  _count 线程获取锁的次数,每加锁一次该值加1。
    • _waiters 当前有多少处于wait状态的thread
    • _recursions 锁的重入次数 
    • _owner 指向持有objectmonitor对象的线程地址。 
    • _waitset 存放调用wait方法,而进入等待状态的线程的队列。
    • _entrylist 处于等待加锁block状态的线程,会被加入到该列表

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

    • _owner指向持有objectmonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _entrylist 集合
    • 当线程获取到对象的monitor 后进入 _owner 区域并把monitor中的owner变量设置为当前线程的同时,monitor中的计数器count加1,
    • 若已经获取锁的线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 waitset集合中等待被唤醒。
    • 若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。

    下节将会介绍一下synchronized的锁优化和锁升级过程

    到此这篇关于java并发编程深入理解之synchronized的使用及底层原理详解 上的文章就介绍到这了,更多相关java 并发编程 synchronized内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!