文档章节

Java并发编程, synchronized原理解析

郑加威
 郑加威
发布于 2018/03/17 12:15
字数 1754
阅读 257
收藏 8

Synchronized的基本使用

  Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。从语法上讲,Synchronized总共有三种用法:

  (1)对于修饰普通方法,锁是当前实例对象。

  (2)对于修饰静态方法,锁是当前类的Class对象。

  (3)对于修饰代码块,锁是Synchonized括号里配置的对象。

Synchronized 原理

synchronized 是 JVM底层实现,在抛出异常时会自动释放锁,而 Lock 是需要手动释放锁,否则锁一直占有,诸如此类。

JDK1.5 之前的版本,Synchronized 一直是重量级锁,它的底层实现(可以参考上图),当多个线程执行到 Synchronized 修饰代码时,其中一个线程获取锁,其它线程在 JVM 维护的一个虚拟列表中等待,当锁被释放时会唤醒等待中的线程,这些等待中的线程是一种竞争上岗,非公平锁的实现。JDK1.6 对 Synchronized 进行了优化,引入了轻量级锁,偏向锁,自旋锁的实现。

  • synchronized作用在方法上时,锁住的便是对象实例(this);
  • 当作用在静态方法时锁住的便是对象对应的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;当synchronized作用于某一个对象实例时,锁住的便是对应的代码块。
  • 在HotSpot JVM实现中,锁有个专门的名字:对象监视器(monitor)。 
  • synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例 。 

下面我们来说说synchronized在JVM中工作过程

public class SynchronizedThis {

    public void methodA(){
        synchronized (this) {
            System.out.println("this A start:"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("this A end:"+Thread.currentThread().getName());
        }
    }

    public synchronized void methodB(){
        System.out.println("this B start:"+Thread.currentThread().getName());
        System.out.println("this B end:"+Thread.currentThread().getName());
    }
}

首先看下synchronized反汇编结果

D:\abc\src\com\thread>javac SynchronizedThis.java
D:\abc\src\com\thread>javap -c SynchronizedThis.class
Compiled from "SynchronizedThis.java"
public class com.thread.SynchronizedThis {
  public com.thread.SynchronizedThis();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public void methodA();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter //进入到一个同步线程,这个线程被锁住
       4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: new           #3                  // class java/lang/StringBuilder
      10: dup
      11: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      14: ldc           #5                  // String this A start:
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokestatic  #7                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      22: invokevirtual #8                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      25: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      28: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      31: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      34: ldc2_w        #11                 // long 1000l
      37: invokestatic  #13                 // Method java/lang/Thread.sleep:(J)V
      40: goto          48
      43: astore_2
      44: aload_2
      45: invokevirtual #15                 // Method java/lang/InterruptedException.printStackTrace:()V
      48: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      51: new           #3                  // class java/lang/StringBuilder
      54: dup
      55: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      58: ldc           #16                 // String this A end:
      60: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      63: invokestatic  #7                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      66: invokevirtual #8                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      69: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      72: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      75: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      78: aload_1
      79: monitorexit
      80: goto          88
      83: astore_3
      84: aload_1
      85: monitorexit // 离开线程,释放锁
      86: aload_3
      87: athrow
      88: return
    Exception table:
       from    to  target type
          34    40    43   Class java/lang/InterruptedException
           4    80    83   any
          83    86    83   any
  public synchronized void methodB();
    Code:
       0: getstatic     #2   //  获取指定类的静态域,并将其值压入栈顶   // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #17                 // String this B start:
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: invokestatic  #7                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      18: invokevirtual #8                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      21: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      27: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      30: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      33: new           #3                  // class java/lang/StringBuilder
      36: dup
      37: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      40: ldc           #18                 // String this B end:
      42: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      45: invokestatic  #7                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      48: invokevirtual #8                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      51: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      54: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: return
}

上面全是JVM的一些命令,JVM的具体执行过程我也是一头雾水,欢迎一起探讨学习。 
MethodA 是synchronized(this)通过反汇编可以看出是通过monitor来控制线程访问,但是MethodB没有体现出monitor来,有清楚的大牛们可以多多留言指点,谢谢 !

我通过网上查询资料:synchronized方法反汇编之后,在常量池中会有ACC_SYNCHRONIZED标示符, JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

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

总结:

  • 在JVM中synchronized是通过监视器(monitor)来控制线程的访问,每个对象都会有一个monitor,如果monitor为0,线程可以进入monitor执行,然后monitor置为1,该monitor被该线程持有,如果该线程再次访问可以继续执行,并且monitor累计加1。
  • 此时如果其他线程要访问monitor,由于该monitor被上个线程占用,其他线程将被阻塞,知道上个线程运行完毕并且monitor置为0,该线程才能访问。 
  • 因此,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

© 著作权归作者所有

郑加威
粉丝 175
博文 183
码字总数 387300
作品 0
杭州
架构师
私信 提问
Java 并发编程源码解析汇总篇

java并发编程,内存模型 java并发编程,volatile内存实现和原理 Java并发编程,并发基础 Java 并发编程,线程池(ThreadPoolExecutor)源码解析 Java并发编程,Executor 框架介绍 Java并发编...

郑加威
2018/12/23
0
0
「原创」Java并发编程系列01 开篇获奖感言

  全网都是复制粘贴的文章,师长这里一直坚持输出原创   点击上方“java进阶架构师”,选择右上角“置顶公众号   不要错过每一天的原创!      为什么要学并发编程   我曾听一个...

java进阶架构师
09/28
0
0
【Java并发专题】27篇文章详细总结Java并发基础知识

努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! github:https://github.com/CL0610/Java-concurrency,欢迎题issue和Pull request。所有的文档都是自己亲自码的,如果觉...

你听___
2018/05/06
0
0
BAT等公司必问的8道Java经典面试题,你都会了吗?

工作多年以及在面试中,我经常能体会到,有些面试者确实是认真努力工作,但坦白说表现出的能力水平却不足以通过面试,通常是两方面原因: 1、“知其然不知其所以然”。做了多年技术,开发了很...

java填坑路
01/06
0
0
再有人问你synchronized是什么,就把这篇文章发给他。

在《深入理解Java虚拟机》中,有这样一段话: synchronized关键字在需要原子性、可见性和有序性这三种特性的时候都可以作为其中一种解决方案,看起来是“万能”的。的确,大部分并发控制操作...

Java填坑之路
2018/08/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

3_数组

3_数组

行者终成事
今天
7
0
经典系统设计面试题解析:如何设计TinyURL(二)

原文链接:https://www.educative.io/courses/grokking-the-system-design-interview/m2ygV4E81AR 编者注:本文以一道经典的系统设计面试题:《如何设计TinyURL》的参考答案和解析为例,帮助...

APEMESH
今天
7
0
使用logstash同步MySQL数据到ES

概述   在生成业务常有将MySQL数据同步到ES的需求,如果需要很高的定制化,往往需要开发同步程序用于处理数据。但没有特殊业务需求,官方提供的logstash就很有优势了。   在使用logstas...

zxiaofan666
今天
10
0
X-MSG-IM-分布式信令跟踪能力

经过一周多的鏖战, X-MSG-IM的分布式信令跟踪能力已基本具备, 特点是: 实时. 只有要RX/TX就会实时产生信令跟踪事件, 先入kafka, 再入influxdb待查. 同时提供实时sub/pub接口. 完备. 可以完整...

dev5
今天
7
0
OpenJDK之CyclicBarrier

OpenJDK8,本人看的是openJDK。以前就看过,只是经常忘记,所以记录下 图1 CyclicBarrier是Doug Lea在JDK1.5中引入的,作用就不详细描述了,主要有如下俩个方法使用: await()方法,如果当前线...

克虏伯
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部