关于synchronized,本文从使用方法,底层原理和锁的升级优化这几个方面来介绍。

1.synchronized的使用:

synchronized可以保证在同一时刻,只有一个线程可以操作共享变量,并且该共享变量的变化对其他线程可见。它的使用方法有三种:

1.1 作用于实例方法

当synchronized作用于实例方法时,它的锁是当前的实例对象。通过以下demo来看下它的用法:

  1. public class SynchronizedDemo implements Runnable{
  2.  
  3. static int i = 0;

  4. // 作用于实例方法
  5. public synchronized void increase(){
  6. i++;
  7. }
  8.  
  9. @Override
  10. public void run() {
  11. for (int i = 0; i < 10000; i++) {
  12. increase();
  13. }
  14. }
  15.  
  16. public static void main(String[] args) throws InterruptedException {
  17. SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
  18. Thread t1 = new Thread(synchronizedDemo);
  19. Thread t2 = new Thread(synchronizedDemo);
  20. t1.start();
  21. t2.start();
  22. t1.join();
  23. t2.join();
  24. System.out.println(i);
  25. }
  26.  
  27. }

以上demo的输出结果是:20000;在java中,任意一个对象都可以作为锁,注意在这里线程t1和t2共用了一把锁,都是synchronizedDemo这个对象。再看下,如下demo:

  1. public class SynchronizedDemo implements Runnable{
  2.  
  3. static int i = 0;
  4.  
  5. public synchronized void increase(){
  6. i++;
  7. }
  8.  
  9. @Override
  10. public void run() {
  11. for (int i = 0; i < 10000; i++) {
  12. increase();
  13. }
  14. }
  15.  
  16. public static void main(String[] args) throws InterruptedException {
  17. SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
  18. SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo();
  19. Thread t1 = new Thread(synchronizedDemo);
  20. Thread t2 = new Thread(synchronizedDemo2);
  21. t1.start();
  22. t2.start();
  23. t1.join();
  24. t2.join();
  25. System.out.println(i);
  26. }
  27.  
  28. }

这个demo会出现什么结果呢?通过多次运行我们发现,输出的值可能会小于20000;原因就是线程t1和t2使用了各自的锁,那么synchronized的存在就毫无意义了,无法保证线程安全。那么针对这种有多个对象(锁)的情况,如何解决呢?将synchronized作用于静态方法就行了。

1.2 作用于静态方法

当作用于静态方法时,锁是当前类的class对象。看如下demo:

  1. public class SynchronizedDemo implements Runnable{
  2.  
  3. static int i = 0;
  4.  
  5. public static synchronized void increase(){
  6. i++;
  7. }
  8.  
  9. @Override
  10. public void run() {
  11. for (int i = 0; i < 10000; i++) {
  12. increase();
  13. }
  14. }
  15.  
  16. public static void main(String[] args) throws InterruptedException {
  17. SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
  18. SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo();
  19. Thread t1 = new Thread(synchronizedDemo);
  20. Thread t2 = new Thread(synchronizedDemo2);
  21. t1.start();
  22. t2.start();
  23. t1.join();
  24. t2.join();
  25. System.out.println(i);
  26. }
  27.  
  28. }

运行结果:20000;此时是线程安全的,因为t1和t2共用了同一个锁,该锁就是SynchronizedDemo的class对象。

1.3 作用于代码块

当作用于代码块时,锁是synchronized括号里配置的对象。对于作用于代码块的使用场景是这样的:如果一个方法体很大,里面有一些耗时操作,但是我们需要同步的仅仅是一部分代码,如果对整个方法进行同步,显然是不合理的,所以可以针对代码块做同步。

  1. * @date 2018925
  2. */
  3.  
  4. public class SynchronizedDemo implements Runnable{
  5.  
  6. static int x = 0;
  7.  
  8. public void run() {
  9.  
  10. //其他耗时操作。。。
  11.  
  12. synchronized (SynchronizedDemo.class) {
  13. for (int i = 0; i < 10000; i++) {
  14. x++;
  15. }
  16. }
  17.  
  18. //其他耗时操作。。。
  19. }
  20.  
  21. public static void main(String[] args) throws InterruptedException {
  22. SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
  23. Thread t1 = new Thread(synchronizedDemo);
  24. Thread t2 = new Thread(synchronizedDemo);
  25. t1.start();
  26. t2.start();
  27. t1.join();
  28. t2.join();
  29. System.out.println(x);
  30. }
  31.  
  32. }

在这个demo中,锁对象是SynchronizedDemo.class,当然可以是任意的java对象。

2.synchronized底层原理

synchronized在JVM中的实现原理,是基于进入和退出Monitor对象来实现方法同步和代码块同步,但是两者的实现细节不同;代码块同步是基于monitorenter和monitorexit指令来实现的,而方法同步是使用另外一种方式。在详解介绍之前,先了解下java对象头。

2.1 java对象头

在JVM中,对象在内存中的布局分为三块区域:对象头,实例变量和填充数据。而synchronized使用的锁对象就是存放在java对象头中的,对于java对象头由Mark Word和Class MetaData Address组成,如果当前对象是数据,则还有Array Length。如下图:

长度 内容 说明
32/64bit Mark Word 存储对象的hashCode,锁信息,分代年龄或者GC标志信息
32/64bit Class MetaData Address 存储到对象类型数据的指针,JVM通过这个指针能确定该对象是哪个类的实例
32/64bit Array Length 如果当前对象是数组,则表示数组的长度

对于32位的JVM,Mark Word默认的存储结构如下:

锁状态 25bit 4bit 1bit是否是偏向锁 2bit锁标志位
无锁状态 对象的hashCode 对象分代年龄 0 01

在32位JVM下,除了上面的Mark Word默认的存储结构外,还有如下可变的的结构:

锁状态 25bit 4bit 1bit 2bit
23bit 2bit 是否是偏向锁 锁标志位
轻量级锁 指向栈中锁记录的指针 00
重量级锁 指向重量级锁的指针 10
GC标记 11
偏向锁 线程ID Epoch 对象粉黛年龄 1 01

在这里,synchronized的对象锁,锁标志位10,指针指向的是monitor对象的起始地址,每一个对象都有一个monitor对象与之关联。当一个monitor对象被一个线程持有后,它就处于锁定状态。

2.2 同步代码块底层原理

对于1.3中的demo,经过javap反编译后得到如下结果:

  1. public class SynchronizedDemo implements java.lang.Runnable {
  2. static int x;
  3.  
  4. public SynchronizedDemo();
  5. Code:
  6. 0: aload_0
  7. 1: invokespecial #1 // Method java/lang/Object."<init>":
  8. ()V
  9. 4: return
  10.  
  11. public void run();
  12. Code:
  13. 0: ldc_w #2 // class SynchronizedDemo
  14. 3: dup
  15. 4: astore_1
  16. 5: monitorenter // 进入同步方法
  17. 6: iconst_0
  18. 7: istore_2
  19. 8: iload_2
  20. 9: sipush 10000
  21. 12: if_icmpge 29
  22. 15: getstatic #3 // Field x:I
  23. 18: iconst_1
  24. 19: iadd
  25. 20: putstatic #3 // Field x:I
  26. 23: iinc 2, 1
  27. 26: goto 8
  28. 29: aload_1
  29. 30: monitorexit // 退出同步方法
  30. 31: goto 39
  31. 34: astore_3
  32. 35: aload_1
  33. 36: monitorexit // 退出同步方法
  34. 37: aload_3
  35. 38: athrow
  36. 39: return
  37. Exception table:
  38. from to target type
  39. 6 31 34 any
  40. 34 37 34 any
  41.  
  42. public static void main(java.lang.String[]) throws java.lang.InterruptedExcept
  43. ion;
  44. Code:
  45. 0: new #2 // class SynchronizedDemo
  46. 3: dup
  47. 4: invokespecial #4 // Method "<init>":()V
  48. 7: astore_1
  49. 8: new #5 // class java/lang/Thread
  50. 11: dup
  51. 12: aload_1
  52. 13: invokespecial #6 // Method java/lang/Thread."<init>":
  53. (Ljava/lang/Runnable;)V
  54. 16: astore_2
  55. 17: new #5 // class java/lang/Thread
  56. 20: dup
  57. 21: aload_1
  58. 22: invokespecial #6 // Method java/lang/Thread."<init>":
  59. (Ljava/lang/Runnable;)V
  60. 25: astore_3
  61. 26: aload_2
  62. 27: invokevirtual #7 // Method java/lang/Thread.start:()V
  63.  
  64. 30: aload_3
  65. 31: invokevirtual #7 // Method java/lang/Thread.start:()V
  66.  
  67. 34: aload_2
  68. 35: invokevirtual #8 // Method java/lang/Thread.join:()V
  69. 38: aload_3
  70. 39: invokevirtual #8 // Method java/lang/Thread.join:()V
  71. 42: getstatic #9 // Field java/lang/System.out:Ljava/
  72. io/PrintStream;
  73. 45: getstatic #3 // Field x:I
  74. 48: invokevirtual #10 // Method java/io/PrintStream.printl
  75. n:(I)V
  76. 51: return
  77.  
  78. static {};
  79. Code:
  80. 0: iconst_0
  81. 1: putstatic #3 // Field x:I
  82. 4: return
  83. }

monitorenter指令是在编译后插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置和异常处,当线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获取对象的锁。当monitorexit指令被执行时,执行线程会释放monitor锁。在上面的代码中可以看到,还有一个monitorexit指令,是在异常结束时执行的指令以释放monitor锁。对于同步方法的底层原理,细节实现上和这不同,这里暂时不做叙述。

3 锁的优化

在java SE1.6中,引入了偏向锁和轻量级锁,锁一共有四种状态,从低到高是:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。这几种状态会随着竞争的提高,锁不断升级,但是不能降级。

3.1 偏向锁

经研究发现,大多数情况下,锁不仅不存在竞争,而且总是由同一个线程多次获得。为了让线程获得锁的代价更低,所以就引入了偏向锁。当一个线程访问代码块并获取锁时,会在对象头和栈帧的锁记录里存储锁偏向的线程ID,以后线程当再次进入同步代码块时,不需要再加锁和解锁,只需要测试下该对象头中是否存储着指向该线程的偏向锁即可。注意,当没有锁竞争时,偏向锁有很好的优化效果,但是一旦锁竞争激烈,偏向锁就会失效,升级为轻量级锁。

3.1.1 偏向锁的撤销

偏向锁使用了一种竞争出现才释放锁的机制,所以当其他线程竞争偏向锁时,持有该偏向锁的线程才会释放锁,我们称之为偏向锁的撤销。流程如下:如果A线程正在持有一个偏向锁,当B线程竞争该偏向锁时,会暂停A线程,然后检查A线程是否还活着,如果A线程不处于活动状态,则将对象头设置为无锁状态;如果A线程还处于活动状态,则将对象头的锁偏向于B线程或者恢复到无锁,最后,唤醒A线程。

3.2 轻量级锁

3.2.1 轻量级锁加锁

线程尝试使用CAS将对象头中Mark Word替换为指向锁记录中的指针,如果成功,则获取锁成功。如果失败,则继续通过自旋CAS来获取锁。

3.2.2 轻量级锁解锁

轻量级锁升级到重量级锁,是在轻量级锁解锁的过程中发生的。线程在获取锁的时候拷贝了对象头中的Mark Word;在它释放锁的时候发现在它持有锁期间有其它线程尝试获取锁,并且该线程对Mark Word做了修改,两者发现不一致,则切换到重量级锁。

参考资料:《java并发编程的艺术》

synchronized详解的更多相关文章

  1. 黑马------synchronized详解

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA线程-synchronized详解 一.synchronized概述 1.线程间实现互斥,必须使用同一个监视器(一个对象 ...

  2. Java synchronized 详解

    Java synchronized 详解 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 1.当两个并发线程访问同一个对象object ...

  3. 剑指Offer——线程同步volatile与synchronized详解

    (转)Java面试--线程同步volatile与synchronized详解 0. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现 ...

  4. JAVA 中 synchronized 详解

    看到一篇关于JAVA中synchronized的用法的详解,觉得不错遂转载之..... 原文地址: http://www.cnblogs.com/GnagWang/archive/2011/02/27 ...

  5. java并发编程(七)synchronized详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.     一.当两个并发线程访问同一个对象object中的这个synchronized( ...

  6. java synchronized详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...

  7. Java中synchronized详解

    synchronized 原则: 尽量避免无谓的同步控制,同步需要系统开销,可能造成死锁 尽量减少锁的粒度 同步方法 public synchronized void printVal(int v) ...

  8. [zt]java synchronized详解

    作者:GangWang 出处:http://www.cnblogs.com/GnagWang/ 记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多 ...

  9. JAVA多线程synchronized详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 当两个并发线程访问同一个对象object中的这个synchronized(this)同 ...

随机推荐

  1. WPF DesiredSize & RenderSize

    DesiredSize DesiredSize介绍 关于DesiredSize的介绍,可以查看最新微软文档对DesiredSize的介绍 DesiredSize,指的是元素在布局过程中计算所需要的大小 ...

  2. IOC的理解,整合AOP,解耦对Service层和Dal层的依赖

    DIP依赖倒置原则:系统架构时,高层模块不应该依赖于低层模块,二者通过抽象来依赖依赖抽象,而不是细节 贯彻依赖倒置原则,左边能抽象,右边实例化的时候不能直接用抽象,所以需要借助一个第三方 高层本来是依 ...

  3. 基于 Docker 的微服务架构实践

    本文来自作者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践} 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 D ...

  4. SQL使用总结

    本文为转载:对于SQL的学习与使用,推荐大家去这儿,讲的很系统: http://www.w3school.com.cn/sql/index.asp 练习SQL的使用,推荐大家去这里: https:// ...

  5. 中间件(3)NoSQL

    NoSQL最常见的解释是non-relational,或者not only SQL,从字段意思上就可以看出,它是指非关系型数据库的统称. NoSQL诞生的背景 随着大型网站分布式架构的发展,使用传统关 ...

  6. HTML中特殊符号

  7. CTSC 2018酱油记

    Day0 5.5 花了一上午的时间把codechef div2的前四题切了,又在zbq老司机的指导下把第五题切了 中午12:00 gryz电竞组从机房出发,临走的时候看到很多学长挺恋恋不舍的,毕竟可能 ...

  8. matlab练习程序(渲染三原色)

    这里我用的空间是x向右为正,y向下为正,z向屏幕里面为正.相当于标准右手系绕x轴旋转了180度. 将三个点光源放在 r = [0.3,0,0.5];g = [0.3,-0.5*cos(pi/6),-0 ...

  9. 批量删除MSSQL 中主外键约束

    转自: http://www.maomao365.com/?p=813 在制作 MSSQL同步工具的时候,发现由于主外键的约束,导致数据同步异常,所有我们需要把 读数据库里面的主外键约束,进行批量删除 ...

  10. 启动期间的内存管理之引导分配器bootmem--Linux内存管理(十)

    在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检 ...