首页 > 代码库 > 重新认识synchronized(下)

重新认识synchronized(下)

技术分享
synchronized既保证原子性,又保证内存可见性,是一种线程同步的方式,是锁机制的一种java实现。synchronized的实现基于JVM底层,JVM是基于monitor实现的,而monitor的实现依赖于操作系统的互斥实现。

语义

synchronized语义是同步,但同步有两层含义:

  1. 互斥,即锁的特点。同一时间只能有一个线程持有监视器,因此一旦线程进入监视器保护的代码块(即临界区),其他线程是不允许监视器保护的代码块,直到前一个线程退出代码块。互斥阻止了其他线程看到对象不一致的状态,与原子性有相同的语义。
  2. 可见。synchronized保证进入同步代码块的线程,都可以看到由同一个锁保护的之前所有的修改效果。原因是:在释放监视器时(即退出同步代码块),会将工作内存中未映射到主内存的工作拷贝,强制刷新回主内容。在获取监视器是(即进入同步代码块时),监视器会使本地内存失效,强制从主内存拷贝到工作内存。

互斥保证在线程退出前,所有对象状态变更都对其他线程不可见;可见保证在线程进入同步代码块时,可以看到上一个线程对对象状态变更的最终状态。

线程安全与同步

线程安全表明在多线程环境中,不会有多个线程同时访问共享数据。
线程同步是线程访问类和实例字段变量,和其他共享资源的一种串行化行为,确保在同一时间只能有一个线程访问资源。举个栗子,春运火车票只剩下最后一张火车票,A,B都要抢这张火车票,怎么解决这个问题防止超卖呢?把资源保护起来,让A,B排队来买火车票。
线程安全是属性,线程同步是方式。

指令

synchronized同步代码块是通过monitorenter和monitorexit指令实现的,而synchronized同步方法是基于ACC_SYCHRONIZED标志,同步方法被调用时JVM会检查这个标志。monitorenter标记临界区的开始,线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权;monitorexit标记临界区的结束,线程执行到 monitorexit 指令时,将释放对象所对应的 monitor 的所有权。

1 public class SynchronizedMethod {2     public synchronized void methodA() {3         System.out.println("MethodA start");4 5     }6 }

将这段代码通过 javap -c 反编译一下,重点关注一下编译后的第3行和第13行。

 1 Compiled from "SynchronizedTest.java" 2 public class com.memory.SynchronizedTest { 3   public com.memory.SynchronizedTest(); 4     Code: 5        0: aload_0        6        1: invokespecial #1                  // Method java/lang/Object."<init>":()V 7        4: return         8  9   public void methodA();10     Code:11        0: aload_0       12        1: dup           13        2: astore_1      14        3: monitorenter  15        4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;16        7: ldc           #3                  // String MethodA start17        9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V18       12: aload_1       19       13: monitorexit   20       14: goto          2221       17: astore_2      22       18: aload_1       23       19: monitorexit   24       20: aload_2       25       21: athrow        26       22: return        27     Exception table:28        from    to  target type29            4    14    17   any30           17    20    17   any31 }

锁的种类

技术分享java synchronized锁升级
JDK1.6中对synchronized优化引入了偏向锁,轻量级锁,重量级锁。锁的升级过程是单方向的,只允许从低到高升级,不允许降级。

重量级锁(Heavyweight Lock)是将程序运行交出控制权,将线程挂起,由操作系统来负责线程间的调度,负责线程的阻塞和执行。这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,消耗大量的系统资源,导致性能低下。

轻量级锁(lightweight Locking)是相对于重量级锁而言的,在synchronized实现中使用自旋的方式,实际是通过CPU自旋等待的方式替代线程切换,竞争的线程不会因此而阻塞,避免阻塞唤醒造成的CPU负荷。采用自旋的方式有利有弊,当锁占用的时间较短时,较少次数的自旋等待就可以获取锁;但在锁占用的时间较长时,自旋会白白浪费大量的CPU资源。因此自旋的次数有一定要在限定之内,自旋失败就会立即将锁升级为重量级锁,称为锁膨胀。

偏向锁(Biased Locking )从字面含义是这把锁是有私心的,会倾向于上次访问的线程。Hotspot的作者在他的论文《QRL-OpLocks-BiasedLocking》中阐述到,研究发现大多数情况下不存在多线程争夺共享资源,而且总是由同一线程多次获得,考虑到CAS (Compare-And-Swap)指令在获取Java监视器时会造成较大的CPU延迟,为了让线程获得锁的代价更低而引入了偏向锁。

锁标记的位置

64位虚拟机中,标记字段(Mark Word)中包含哈希吗(HashCode,存放31bits对象的hashcode值),GC分代年龄(Generational GC Age,4bits,因此分代年龄最高为15),偏向线程ID,偏向锁标记。
synchronized锁的四个状态:无锁状态,偏向锁,轻量级锁和重量级锁,在Mark Word中对应不同的字段。
技术分享java synchronized不同级别锁中的Mark Word

我是葛一凡,希望对你有用。

参考

  1. JSR 133 (Java Memory Model) FAQ
  2. Java内存模型(一)
  3. Java 锁机机制——浅析 Synchronized
  4. Biased Locking in HotSpot
  5. Java Synchronised机制
  6. 深入JVM锁机制1-synchronized
  7. 多线程之:偏向锁,轻量级锁,重量级锁
  8. JVM内部细节之一:synchronized关键字及实现细节(轻量级锁Lightweight Locking)
  9. java锁优化
  10. Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
  11. 虚拟机中的锁优化简介(适应性自旋/锁粗化/锁削除/轻量级锁/偏向锁)
  12. JVM内部细节之二:偏向锁(Biased Locking)
  13. 小议偏向锁
  14. synchronized、锁、多线程同步的原理是咋样的
  15. Java中的锁
  16. markOop.hpp
  17. 5 Things You Didn’t Know About Synchronization in Java and Scala
  18. 论文:The Smart Energy Management of Multithreaded Java Applications on Multi-Core Processors

 

重新认识synchronized(下)