JAVA并发之synchronized关键字

简介

synchronizaed关键字是JAVA阻塞同步(互斥同步)中最常用的一种方式,使用时将此关键字加到所需同步的代码块儿前即可,比如

int i = 0;
synchronized (this){
    i++;
}

synchronizaed同步方式在JAVA中是重量级加锁方式,下面来介绍一下它的工作原理,首先写一段代码:

public class Sync {
    synchronized void syncMethod(){}
    public void add(){
        synchronized (this) {
        }
    }
}

我在这段代码中定义了一个同步方法,并在另一个非同步方法中定义了一个同步代码块,接下来反编译class文件看下字节码指令:

  synchronized void syncMethod();
    descriptor: ()V
    flags: ACC_SYNCHRONIZED//同步标志位!
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 4: 0

  public void add();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter//同步代码块进入
         4: aload_1
         5: monitorexit//同步代码块退出
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit//同步代码块异常退出
        12: aload_2
        13: athrow
        14: return
      Exception table:
         from    to  target type
             4     6     9   any
             9    12     9   any
      LineNumberTable:
        line 6: 0
        line 8: 4
        line 9: 14
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class com/Sync, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

可以看到同步方法的同步方式是隐式的,它在方法的flags标志中添加了ACC_SYNCHRONIZED标志,程序执行中,对某方法的调用会判断这个标志位是否存在并判断是否应该同步。

同步代码块的同步方式则是显式的,它会在同步块的前后分别生成monitorenter和monitorexit指令,这两个指令是同步块的关键,他们会在进出同步块时分别通过monitor机制做标记,表示获取或者释放,这样保证了同步的进行。下面引用Java虚拟机规范的一段话来说明:

   Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。

 方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先持有管程,然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获得同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放。

 同步一段指令集序列通常是由Java语言中的synchronized块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要编译器与Java虚拟机两者协作支持。

 Java虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现。无论是显式同步(有明确的monitorenter和monitorexit指令)还是隐式同步(依赖方法调用和返回指令实现的)都是如此。

 编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须有执行其对应monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行monitorexit指令。

那么monitor机制是什么呢?

monitor机制

monitor机制是synchronized关键字的具体实现方式,java对象天生就有monitor机制,个人理解monitor机制是C++编写的objectMonitor.hpp和Java对象头信息共同实现的一种同步判断机制,我们先看下objectMonitor.hpp文件:

  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;//重入机制
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

其中_WaitSet字段是 LL of threads wait()ing on the monitor,就是等待在该监视器的线程集合,其他的属性在源码中也均有注释,有兴趣可以在http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/bfac16f18d92/src/share/vm/runtime/objectMonitor.hpp中查看研究。这段代码中的字段定义了对象的占用线程,等待集合以及阻塞时的自旋等待方式等内容。

我们再来看一下Java对象头,Java对象的内存分布如下图所示:

https://www.cnblogs.com/ZoHy/p/11313155.html

Java对象中有一部分信息叫对象头,这部分信息负责存储对象自身的运行时数据和指向方法区对象类型数据的指针,其中MarkWord和Java的锁机制关系颇深,这一部分内容是可复用的,随着标志位的不同它会存储不同的内容:

当线程试图获取锁(进入同步区)时,我认为工作机制是检测对象头信息以及底层的objectMonitor字段,判断是否有被抢占,如果被占用则判断是否是线程重入,如果不是则需要阻塞等待,阻塞是通过自旋来实现的,至于获取成功以及一系列的锁优化,请见另一篇文章。

我们来看一下实际工作中线程获取锁的流程,如下图所示:

当一个线程需要获取 Object 的锁时,会被放入 EntrySet 中进行等待,如果该线程获取到了锁,成为当前锁的 owner。如果根据程序逻辑,一个已经获得了锁的线程缺少某些外部条件,而无法继续进行下去(例如生产者发现队列已满或者消费者发现队列为空),那么该线程可以通过调用 wait 方法将锁释放,进入 wait set 中阻塞进行等待,其它线程在这个时候有机会获得锁,去干其它的事情,从而使得之前不成立的外部条件成立,这样先前被阻塞的线程就可以重新进入 EntrySet 去竞争锁。这个外部条件在 monitor 机制中称为条件变量。

链接:https://www.jianshu.com/p/7f8a873d479c

原文地址:https://www.cnblogs.com/lybnumber6/p/12173297.html

时间: 2024-10-07 01:02:33

JAVA并发之synchronized关键字的相关文章

Java并发之synchronized关键字深度解析(二)

前言 本文继续[Java并发之synchronized关键字深度解析(一)]一文而来,着重介绍synchronized几种锁的特性. 一.对象头结构及锁状态标识 synchronized关键字是如何实现的给对象加锁?首先我们要了解一下java中对象的组成.java中的对象由3部分组成,第一部分是对象头,第二部分是实例数据,第三部分是对齐填充. 对齐填充:jvm规定对象的起始内存地址必须是8字节的整数倍,如果不够的话就用占位符来填充,此部分占位符就是对齐填充: 实例数据:实例数据是对象存储的真正有

Java并发之synchronized关键字深度解析(一)

前言 近期研读路神之绝世武学,徜徉于浩瀚无垠知识之海洋,偶有攫取吉光片羽,惶恐未领略其精髓即隐入岁月深处,遂急忙记录一二,顺备来日吹cow之谈资.本小系列为并发之亲儿子-独臂狂侠synchronized专场. 一.使用场景 synchronized是java中的一个关键字,用于给对象加锁,保证在单机的并发条件下线程安全.原子性和可见性是它保证线程安全的基础功能.一定要注意它锁的是对象,而且它是一个排他的非公平可重入锁.本文先从使用的场景上来展现其作用. 1.用在普通方法中 常用的格式如下所示:

Java并发之synchronized关键字和Lock接口

欢迎点赞阅读,一同学习交流,有疑问请留言 . GitHub上也有开源 JavaHouse,欢迎star 引用 当开发过程中,我们遇到并发问题.怎么解决? 一种解决方式,简单粗暴:上锁.将千军万马都给拦下来,只允许一个人过独木桥.书面意思就是将并行的程序变成串行的程序.现实的锁有门锁.挂锁和抽屉锁等等.在Java中,我们的锁就是synchronized关键字和Lock接口. synchronized关键字 synchronized也叫同步锁,是Java里面的关键字.我们可以猜测到synchroni

Java并发之synchronized关键字深度解析(三)

前言 本篇主要介绍一下synchronized的批量重偏向和批量撤销机制,属于深水区,大家提前备好氧气瓶. 说完synchronized锁的膨胀过程,下面我们再延伸一下synchronized锁的两种特殊处理,一种是锁的批量重偏向,一种是锁的批量撤销.JVM中有两个参数,BiasedLockingBulkRebiasThreshold和BiasedLockingBulkRevokeThreshold,前者默认阈值为20,控制批量重偏向:后者默认阈值为40,控制批量撤销.下面我们分别看一下它们是如

巨人大哥谈Java中的Synchronized关键字用法

巨人大哥谈Java中的Synchronized关键字用法 认识synchronized 对于写多线程程序的人来说,经常碰到的就是并发问题,对于容易出现并发问题的地方价格synchronized基本上就搞定 了,如果说不考虑性能问题的话,这一操绝对能应对百分之九十以上的情况,若对于性能方面有要求的话就需要额外的知识比如读写锁等等.本文目的先了解透彻synchronized的基本原理. Synchronized的基本使用 Synchronized的作用主要有三个: (1)确保线程互斥的访问同步代码 

java并发之volatile关键字(TIJ21-21.3.3 21.3.4)

** 1 简介    volatile是java中的一个保留关键字,它在英语中的含义是易变的,不稳定的.volatile像final.static等其他修饰符 一样,可以修饰class中的域,而不能修饰方法中的局部变量.当修饰class中的域时,volatile可以修饰primative类型或者任意对 象.下面这个例子展示了这一点: public class TIJ_volatile {    private volatile int i;    private volatile String

关于Java中的synchronized关键字

[内容简介] 本文主要介绍Java中如何正确的使用synchronized关键字实现线程的互斥锁. [能力需求] 至少已经完整的掌握了Java的语法基础,基本的面向对象知识,及创建并启动线程. [正文] 关于synchronized关键字的使用,很多说法是“锁同一个对象”就可以确保锁是正常的,今天,有人提了一个问题,我觉得非常不错,所以与各位一起分享一下. 在这里,就不提关于线程和synchronized关键字的基本使用了,以非常传统的“银行取钱”的故事为案例,直接上代码:Ps:以下代码是直接敲

Java中利用synchronized关键字实现多线程同步问题

Java 中多线程的同步依靠的是对象锁机制,synchronized关键字就是利用了封装对象锁来实现对共享资源的互斥访问. 下面以一个简单例子来说明多线程同步问题,我们希望在run()方法里加入synchronized关键字来实现互斥访问. package com.clark.thread; public class MyThread implements Runnable{     private int threadId;          public MyThread(int id){

java并发之synchronized

在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问. 在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块. 注意: 1)当一个线程正在