精华内容
下载资源
问答
  • synchronized 原理

    2019-11-14 10:01:58
    synchronized 原理

    synchronized 原理

    展开全文
  • Synchronized原理

    2019-02-22 10:37:51
    Synchronized原理

    Synchronized原理

    展开全文
  • synchronized原理

    万次阅读 多人点赞 2018-08-31 22:17:35
    synchronized的锁的原理 Java对象头 Monitor synchronized锁的优化 自旋锁与自适应自旋 锁消除 锁粗化 偏向锁 轻量级锁 重量级锁 锁升级 wait和notify的原理 wait和notify为什么需要在synchronized里面...

    目录

    synchronized的三种应用方式

    synchronized的字节码指令

    synchronized的锁的原理

    Java对象头

    Monitor

    synchronized锁的优化

    自旋锁与自适应自旋

    锁消除

    锁粗化

    偏向锁

    轻量级锁

    重量级锁

    锁升级

    wait和notify的原理

    wait和notify为什么需要在synchronized里面?


    synchronized的三种应用方式

    1. 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
    2. 修饰静态方法,作用于当前类加锁,进入同步代码前要获得当前类的锁。
    3. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象。

    synchronized的字节码指令

    synchronized同步块使用了monitorenter和monitorexit指令实现同步,这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。

    线程执行到monitorenter指令时,会尝试获取对象所对应的monitor所有权,也就是尝试获取对象的锁,而执行monitorexit,就是释放monitor的所有权。


    synchronized的锁的原理

    两个重要的概念:一个是对象头,另一个是monitor。


    Java对象头

    在Hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头(Mark Word、Class Metadata Address)、实例数据和对齐填充;Java对象头是实现synchronized的锁对象的基础。一般而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键。

    • Mark Word

    Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit)。

     

    • Class Metadata Address

    类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

    • Array length

    如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。


    Monitor

    Monitor是一个同步工具,它内置于每一个Object对象中,相当于一个许可证。拿到许可证即可以进行操作,没有拿到则需要阻塞等待。

    在hotspot虚拟机中,通过ObjectMonitor类来实现monitor。


    synchronized锁的优化

    jdk1.6以后对synchronized的锁进行了优化,引入了偏向锁、轻量级锁,锁的级别从低到高逐步升级: 

    无锁->偏向锁->轻量级锁->重量级锁


    自旋锁与自适应自旋

    线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获得了锁,所以它们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。这样就避免了线程切换的开销,极大的提升了性能。

    而适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。他可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起。


    锁消除

    对不会存在线程安全的锁进行消除。


    锁粗化

    如果jvm检测到有一串零碎的操作都对同一个对象加锁,将会把锁粗化到整个操作外部,如循环体。


    偏向锁

    多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让其获得锁的代价更低而引入了偏向锁。

    当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。

    如果测试成功,表示线程已经获得了锁。

    如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成01(表示当前是偏向锁)。

    如果没有设置,则使用CAS竞争锁。

    如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。


    轻量级锁

    引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,通过CAS竞争锁,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。


    重量级锁

    重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。


    锁升级

    偏向锁升级轻量级锁:当一个对象持有偏向锁,一旦第二个线程访问这个对象,如果产生竞争,偏向锁升级为轻量级锁。

    轻量级锁升级重量级锁:一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。


    wait和notify的原理

    调用wait方法,首先会获取监视器锁,获得成功以后,会让当前线程进入等待状态进入等待队列并且释放锁。

    当其他线程调用notify后,会选择从等待队列中唤醒一个线程,而执行完notify方法以后,并不会立马唤醒线程,原因是当前的线程仍然持有这把锁,处于等待状态的线程无法获得锁。必须要等到当前的线程执行完按monitorexit指令以后,也就是锁被释放以后,处于等待队列中的线程就可以开始竞争锁了。


    wait和notify为什么需要在synchronized里面?

    wait方法的语义有两个,一个是释放当前的对象锁、另一个是使得当前线程进入阻塞队列,而这些操作都和监视器是相关的,所以wait必须要获得一个监视器锁。

    而对于notify来说也是一样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里,所以就必须要找到这个对象获取到这个对象的锁,然后到这个对象的等待队列中去唤醒一个线程。

    展开全文
  • Synchronized 原理

    2020-06-27 18:34:13
    Synchronized 原理 如果对上面的执行结果还有疑问,也先不用急,我们先来了解 Synchronized原理。 再回头上面的问题就一目了然了。我们先通过反编译下面的代码来看看 Synchronized 是如何实现对代码块进行同步...

     

    Synchronized 原理

    如果对上面的执行结果还有疑问,也先不用急,我们先来了解 Synchronized 的原理。

    再回头上面的问题就一目了然了。我们先通过反编译下面的代码来看看 Synchronized 是如何实现对代码块进行同步的:

    
     
    1. package com.paddx.test.concurrent; 
    2. public class SynchronizedMethod { 
    3.   public synchronized void method() { 
    4.     System.out.println("Hello World!"); 
    5.   } 

    反编译结果:

    关于这两条指令的作用,我们直接参考 JVM 规范中描述:

    monitorenter :Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

    •  If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
    •  If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
    • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

    这段话的大概意思为:每个对象有一个监视器锁(Monitor),当 Monitor 被占用时就会处于锁定状态。

    线程执行 Monitorenter 指令时尝试获取 Monitor 的所有权,过程如下:

    • 如果 Monitor 的进入数为 0,则该线程进入 Monitor,然后将进入数设置为 1,该线程即为 Monitor 的所有者。
    • 如果线程已经占有该 Monitor,只是重新进入,则进入 Monitor 的进入数加 1。
    • 如果其他线程已经占用了 Monitor,则该线程进入阻塞状态,直到 Monitor 的进入数为 0,再重新尝试获取 Monitor 的所有权。

    monitorexit:The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

    The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner.

    Other threads that are blocking to enter the monitor are allowed to attempt to do so.

    这段话的大概意思为:执行 Monitorexit 的线程必须是 Objectref 所对应的 Monitor 的所有者。

    指令执行时,Monitor 的进入数减 1,如果减 1 后进入数为 0,那线程退出 Monitor,不再是这个 Monitor 的所有者。

    其他被这个 Monitor 阻塞的线程可以尝试去获取这个 Monitor 的所有权。

    通过这两段描述,我们应该能很清楚的看出 Synchronized 的实现原理。

    Synchronized 的语义底层是通过一个 Monitor 的对象来完成,其实 Wait/Notify 等方法也依赖于 Monitor 对象。

    这就是为什么只有在同步的块或者方法中才能调用 Wait/Notify 等方法,否则会抛出 java.lang.IllegalMonitorStateException 的异常。

    我们再来看一下同步方法的反编译结果,源代码如下:

    
     
    1. package com.paddx.test.concurrent; 
    2.  
    3. public class SynchronizedMethod { 
    4.   public synchronized void method() { 
    5.     System.out.println("Hello World!"); 
    6.   } 

    反编译结果:

    从反编译的结果来看,方法的同步并没有通过指令 Monitorenter 和 Monitorexit 来完成(理论上其实也可以通过这两条指令来实现)。不过相对于普通方法,其常量池中多了 ACC_SYNCHRONIZED 标示符。

    JVM 就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置。

    如果设置了,执行线程将先获取 Monitor,获取成功之后才能执行方法体,方法执行完后再释放 Monitor。在方法执行期间,其他任何线程都无法再获得同一个 Monitor 对象。

    其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

    运行结果解释

    有了对 Synchronized 原理的认识,再来看上面的程序就可以迎刃而解了。

    ①代码段 2 结果

    虽然 Method1 和 Method2 是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的。

    所以调用之前都需要先去竞争同一个对象上的锁(Monitor),也就只能互斥的获取到锁,因此,Method1 和 Method2 只能顺序的执行。

    ②代码段 3 结果

    虽然 Test 和 Test2 属于不同对象,但是 Test 和 Test2 属于同一个类的不同实例。

    由于 Method1 和 Method2 都属于静态同步方法,所以调用的时候需要获取同一个类上 Monitor(每个类只对应一个 Class 对象),所以也只能顺序的执行。

    ③代码段 4 结果

    对于代码块的同步,实质上需要获取 Synchronized 关键字后面括号中对象的 Monitor。

    由于这段代码中括号的内容都是 This,而 Method1 和 Method2 又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。

    总结

    Synchronized 是 Java 并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。

    但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用 Synchronized 关键字。

    另一方面也能够帮助我们更好的理解并发编程机制,有助于我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,986
精华内容 3,594
关键字:

synchronized原理