七日打卡:synchronized理解

719 阅读4分钟

在多线程场景编程中,经常会出现多个线程对同一份资源的访问的现象,那么这个可以被多个线程同时访问的资源我们称之为临界资源。

设计同步器的意义

  • 资源按照线程的之间的可见性可以分为线程共享资源和线程私有资源。由于线程执行的过程是不可控的,所以需要采用同步机制来保证对对象可变状态(共享资源)的互斥访问。

如何解决线程并发安全问题?

  • 在解决并发问题时,其核心就是序列化访问临界资源。保证在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。 Java中,提供了两种方式来实现同步互斥访问:synchronized 和 Lock,同步器的本质就是加锁。加锁目的:序列化访问临界资源,即同一时刻只能有一个线程访问临界资源(同步互斥访问) 不过有一点需要区别的是:当多个线程执行一个方法时,该方法内部的局部变量并不是临界资源,因为这些局部变量是在每个线程的线程栈中,是线程私有属性,不会导致线程安全问题。

synchronized原理详解

! JDK1.5版本之后对synchronized的进行了一个锁升级的性能优化,在后续版本之中使用synchronized和reentrantlock进行加锁的性能几乎一致。

  • synchronized内置锁是一种对象锁(锁的是对象而非引用),锁粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的锁。一般情况的加锁方式

      1、同步实例方法,锁是当前实例对象 
      2、同步类方法,锁是当前类对象 
      3、同步代码块,锁是括号里面的对象
    
  • synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码 块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低。当然,JVM内置锁在1.5 之后版本做了重大的优化,如:

     -锁粗化(Lock Coarsening)
     锁消除(Lock Elimination)
     轻量级锁(Lightweight Locking)
     偏向锁(Biased Locking)
     适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,
     内置锁的并发性能已经基本与 Lock持平。 
     synchronized关键字被编译成字节码后会被翻译成monitorenter 和 monitorexit 
     两条指令分别在同步块逻辑代码的起始位置与结束位置。
     
    

synchronized关键字被编译成字节码后会被翻译成monitorenter 和 monitorexit

每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示:

  • Monitor监视器锁

    • 任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和 MonitorExit指令来实现。
    • monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行 monitorenter指令时尝试获取monitor的所有权,过程如下:
      • a. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
      • b. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
      • c. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;
    • monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减 1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去 获取这个 monitor 的所有权。
    • monitorexit,指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁,是为了保证对象的锁一定要释放掉;