Java synchronized同步关键字工作原理

目录
  • 一、简介
  • 二、synchronized的特性
  • 三、synchonized的使用及通过反汇编分析其原理
    • 修饰代码块
      • monitorenter指令
      • monitorexit指令
    • 修饰普通方法
    • 修饰静态方法
  • 四、synchronized锁对象存在哪里
  • 五、synchronized与lock的区别
  • 六、总结

一、简介

synchronized是一个同步关键字,在某些多线程场景下,如果不进行同步会导致共享数据不安全,synchronized关键字就可以用于代码同步。

synchronized主要有3种使用形式:

  • 修饰普通同步方法

锁的对象是当前实例对象;

  • 修饰静态同步方法

锁的对象是当前的类的Class字节码对象;

  • 修饰同步代码块

锁的对象是synchronized后面括号里配置的对象,可以是某个对象,也可以是某个类的.class对象;

二、synchronized的特性

1)、原子性

原子性指的是在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行。

2)、可见性

可见性是指一个线程对共享变量进行了修改,另一个线程可以立即读取得到修改后的最新值。

获取锁时,会清空当前线程工作内存中共享变量的副本值,重新从主内存中获取变量最新的值;

释放锁时,会将工作内存的值重新刷新回主内存;

3)、有序性

有序性是指程序中代码的执行顺序,Java在编译时和运行时会对代码进行优化,会导致程序最终的执行顺序不一定就是我们编写代码时的顺序。

例如,instance = new Singleton()实例化对象的语句分为三步:

  • 1、分配对象的内存空间;
  • 2、初始化对象;
  • 3、设置实例对象指向刚分配的内存地址;

上述第二步操作需要依赖第一步,但是第三步操作不需要依赖第二步,所以执行顺序可能为:1->2->3、1->3->2,当执行顺序为1->3->2时,可能实例对象还没正确初始化,我们直接拿到使用的时候可能会报错。

synchronized的有序性是依靠内存屏障实现的,在 monitorenter 指令和 Load 屏障之后,会加一个 Acquire屏障,这个屏障的作用是禁止同步代码块里面的读操作和外面的读写操作之间发生指令重排,在 monitorexit 指令前加一个Release屏障,也是禁止同步代码块里面的写操作和外面的读写操作之间发生重排序。如下:

int a = 0;
synchronize (this){  //monitorenter
    // Load内存屏障
    // Acquire屏障,禁止代码块内部的读,和外面的读写发生指令重排
    int b = a;
    a = 10;    //注意:内部还是会发生指令重排
    // Release屏障,禁止写,和外面的读写发生指令重排
} //monitorexit
//Store内存屏障

4)、可重入特性

可重入指的就是一个线程可以多次执行synchronized,重复获取同一把锁。

举个例子:

public class RenentrantDemo {
    // 锁对象
    private static Object obj = new Object();
    public static void main(String[] args) {
        // 自定义Runnable对象
        Runnable runnable = () -> {
            //  使用嵌套的同步代码块
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName() + "第一次获取锁资源...");
                synchronized (obj) {
                    System.out.println(Thread.currentThread().getName() + "第二次获取锁资源...");
                    synchronized (obj) {
                        System.out.println(Thread.currentThread().getName() + "第三次获取锁资源...");
                    }
                }
            }
        };
        new Thread(runnable, "t1").start();
    }
}

运行结果:

t1第一次获取锁资源...
t1第二次获取锁资源...
t1第三次获取锁资源...

三、synchonized的使用及通过反汇编分析其原理

修饰代码块

public class SynchronizedDemo01 {
    // 锁对象
    private static Object obj = new Object();
    public static void main(String[] args) {
        synchronized (obj) {
            System.out.println("execute main()...");
        }
    }
}

使用javap -p -v .\SynchronizedDemo01.class命令对字节码进行反汇编,查看字节码指令:

monitorenter指令

官网对monitorenter指令的介绍,就是说每一个对象都会和一个监视器对象monitor关联,监视器被占用时会被锁住,其他线程无法来获取该monitor。 当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。大体过程如下:

1. 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1,当前线程成为monitor的owner(拥有这把锁的线程);

2. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1(记录线程拥有锁的次数);

3. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权;

monitorexit指令

官网对monitorexit指令的介绍,就是说能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程;执行monitorexit时会将monitor的进入数减1,当monitor的进入数减为0时,当前线程退出。

为什么字节码中存在两个monitorexit指令?

其实第二个monitorexit指令,是在程序发生异常时候用到的,也就说明了synchronized在发生异常时,会自动释放锁。

ObjectMonitor对象监视器结构如下:

ObjectMonitor() {
    _header       = NULL;		//锁对象的原始对象头
    _count        = 0;			//抢占当前锁的线程数量
    _waiters      = 0,			//调用wait方法后等待的线程数量
    _recursions   = 0;			//记录锁重入次数
    _object       = NULL;
    _owner        = NULL;		//指向持有ObjectMonitor的线程
    _WaitSet      = NULL;		//处于wait状态的线程队列,等待被唤醒
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;		//等待锁的线程队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

修饰普通方法

public class SynchronizedDemo02 {
    public static void main(String[] args) {
    }
    // 修饰普通方法
    public synchronized void add() {
        System.out.println("add...");
    }
}

使用javap -p -v .\SynchronizedDemo02.class命令对字节码进行反汇编,查看字节码指令:

如上图,我们可以看到同步方法在反汇编后,不再是通过插入monitorentry和monitorexit指令实现,而是会增加 ACC_SYNCHRONIZED 标识隐式实现的,如果方法表结构(method_info Structure)中的ACC_SYNCHRONIZED标志被设置,那么线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象,如果monitor对象已经被其它线程获取,那么当前线程被阻塞。

修饰静态方法

public class SynchronizedDemo03 {
    public static void main(String[] args) {
        add();
    }
    // 修饰静态方法
    public synchronized static void add() {
        System.out.println("add...");
    }
}

使用javap -p -v .\SynchronizedDemo03.class命令对字节码进行反汇编,查看字节码指令:

四、synchronized锁对象存在哪里

之前对对象的内存布局的介绍中,我们知道一个对象,包括对象头、实例数据、对齐填充。而对象头又包括mark word标记字、类型指针、数组长度(只有数组对象才有)。在mark word标记字中,有一块区域主要存放关于锁的信息。

存在锁对象的对象头的MarkWord标记字中。如下图:

五、synchronized与lock的区别

区别 synchronized lock
1 关键字 接口
2 自动释放锁 必须手动调用unlock()方法释放锁
3 不能知道线程是否拿到锁 可以知道线程是否拿到锁
4 能锁住方法和代码块 只能锁住代码块
5 读、写操作都阻塞 可以使用读锁,提高多线程读效率;
6 非公平锁 通过构造方法可指定是公平锁/非公平锁

六、总结

1、synchronized修饰代码块的时候,通过在生成的字节码指令中插入monitorenter和monitorexit指令来完成对对象监视器锁的获取和释放;

2、synchronized修饰普通方法和静态方法的时候,通过在字节码中的方法头信息中添ACC_SYNCHRONIZED标识,线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象;

3、synchronized修饰代码块,锁的对象就是代码块中的对象;修饰普通方法的时候,锁的对象就是当前对象this;修饰静态方法的时候,锁的对象就是当前类的Class字节码对象(类对象);

4、使用synchronized修饰实例对象时,如果一个线程正在访问实例对象的一个synchronized方法时,其它线程不仅不能访问该synchronized方法,该对象的其它synchronized方法也不能访问,因为一个对象只有一个监视器锁对象,但是其它线程可以访问该对象的非synchronized方法。

5、线程A访问实例对象的非static synchronized方法时,线程B也可以同时访问实例对象的static synchronized方法,因为前者获取的是实例对象的监视器锁,而后者获取的是类对象的监视器锁,两者不存在互斥关系。

到此这篇关于Java synchronized同步关键字工作原理的文章就介绍到这了,更多相关Java synchronized内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java HashTable与Collections.synchronizedMap源码深入解析

    目录 一.类继承关系图 二.HashTable介绍 三.HashTable和HashMap的对比 1.线程安全 2.插入null 3.容量 4.Hash映射 5.扩容机制 6.结构区别 四.Collections.synchronizedMap解析 1.Collections.synchronizedMap是怎么实现线程安全的 2.SynchronizedMap源码 一.类继承关系图 二.HashTable介绍 HashTable的操作几乎和HashMap一致,主要的区别在于HashTable为

  • Java synchronized重量级锁实现过程浅析

    目录 一.什么是重量级锁 二.重量级锁的演示 三.重量级锁的原理 四.锁的优缺点对比 一.什么是重量级锁 当有大量的线程都在竞争同一把锁的时候,这个时候加的锁,就是重量级锁. 这个重量级锁其实指的就是JVM内部的ObjectMonitor监视器对象: ObjectMonitor() { _header = NULL; //锁对象的原始对象头 _count = 0; //抢占当前锁的线程数量 _waiters = 0, //调用wait方法后等待的线程数量 _recursions = 0; //记

  • Synchronized 和 ReentrantLock 的实现原理及区别

    目录 前言 考点分析 知识扩展 ReentrantLock 源码分析 JDK 1.6 锁优化 自适应自旋锁 锁升级 总结 前言 在 JDK 1.5 之前共享对象的协调机制只有 synchronized 和 volatile,在 JDK 1.5 中增加了新的机制 ReentrantLock,该机制的诞生并不是为了替代 synchronized,而是在 synchronized 不适用的情况下,提供一种可以选择的高级功能. 典型回答: synchronized 属于独占式悲观锁,是通过 JVM 隐式

  • Java Synchronized锁的使用详解

    目录 Synchronized的用法 同步示例方法 同步静态方法 同步代码块 Synchronized的用法 在多线程并发问题中,常用Synchronized锁解决问题.Synchronized锁通常用于同步示例方法,同步静态方法,同步代码块等. 同步示例方法 我们可能自己使用过在方法前加Synchronized锁修饰,在多线程并发同时调用同一个实例化对象时,如果这个方法加上了Synchronized锁,那么也是线程安全的.举个栗子: package Thread; import java.ut

  • Java @Transactional与synchronized使用的问题

    目录 引言 发现问题 问题原因 解决问题 大致思路 @Transactional事务不生效问题 总结 引言 @Transactional是spring通过aop让我们轻松实现事务控制的一个注解:而synchronized是实现同步的java关键字:但是它们两个不能一起使用,一起使用会出现 synchronized失效的问题,这里简单记录一下这个问题: 发现问题 我在impl中实现一个功能逻辑时,为了保证幂等性,在方法中使用synchronized保证同一个用户短时间内多次请求只能串行执行,因为数

  • AQS加锁机制Synchronized相似点详解

    目录 正文 1. Synchronized加锁流程 2. AQS加锁原理 3. 总结 正文 在并发多线程的情况下,为了保证数据安全性,一般我们会对数据进行加锁,通常使用Synchronized或者ReentrantLock同步锁.Synchronized是基于JVM实现,而ReentrantLock是基于Java代码层面实现的,底层是继承的AQS. AQS全称 AbstractQueuedSynchronizer ,即抽象队列同步器,是一种用来构建锁和同步器的框架. 我们常见的并发锁Reentr

  • Java synchronized与CAS使用方式详解

    目录 引言 synchronized synchronized的三种使用方式 synchronized的底层原理 JDK1.6对synchronized的优化 synchronized的等待唤醒机制 CAS 引言 上一篇文章中我们说过,volatile通过lock指令保证了可见性.有序性以及“部分”原子性.但在大部分并发问题中,都需要保证操作的原子性,volatile并不具有该功能,这时就需要通过其他手段来达到线程安全的目的,在Java编程中,我们可以通过锁.synchronized关键字,以及

  • java锁synchronized面试常问总结

    目录 synchronized都问啥? synchronized是什么? synchronized锁什么? synchronized怎么用? 结语 synchronized都问啥? 如果Java面试有什么是必问的,synchronized必定占据一席之地.初出茅庐时synchronized的用法,成长后synchronized的原理,可谓是Java工程师的“一生之敌”. 按照惯例,先来看synchronized的常见问题(在线Excel同步更新中): 根据统计数据可以总结出synchronize

  • Java同步锁Synchronized底层源码和原理剖析(推荐)

    目录 1 synchronized场景回顾 2 反汇编寻找锁实现原理 3 synchronized虚拟机源码 3.1 HotSpot源码Monitor生成 3.2 HotSpot源码之Monitor竞争 3.3 HotSpot源码之Monitor等待 3.4 HotSpot源码之Monitor释放 1 synchronized场景回顾 目标:synchronized回顾(锁分类–>多线程)概念synchronized:是Java中的关键字,是一种同步锁.Java中锁分为以下几种:乐观锁.悲观锁(

  • Java synchronized偏向锁的概念与使用

    目录 一.什么是偏向锁 二.偏向锁原理 三.偏向锁演示 四.偏向锁的处理流程 五.偏向锁的撤销 六.偏向锁的好处 一.什么是偏向锁 HotSpot作者经过研究实践发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁. 偏向锁的“偏”,它的意思是锁会偏向于第一个获得它的线程,会在对象头(Mark Word中)存储锁偏向的线程ID,以后该线程进入和退出同步块时只需要检查是否为偏向锁.锁标志位以及ThreadID即可. 如下图是偏向锁对象头

  • 详解Java Synchronized的实现原理

    目录 Synchronized Synchronized的使用方式 Synchronized的底层实现 1.Java对象头 2.Monitor 3.线程状态流转在Monitor上体现 Synchronized 的锁升级 谈到多线程就不得不谈到Synchronized,重要性不言而喻,今天主要谈谈Synchronized的实现原理. Synchronized synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是同步,也称之为”同步锁“.

  • 95%的Java程序员人都用不好Synchronized详解

    目录 Synchronized锁优化 偏向锁 轻量级锁 获取锁 释放锁 自旋锁 适应自旋锁 锁消除 逃逸分析 重量级锁 三种锁的区别 锁升级 锁粗化 wait和notify的原理 Synchronized锁优化 文章内容整理自 博学谷狂野架构师 jdk1.6对锁的实现引入了大量的优化,如自旋锁.适应性自旋锁.锁消除.锁粗化.偏向锁.轻量级锁等技术来减少锁操作的开销. 锁主要存在四中状态,依次是:无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁,他们会随着竞争的激烈而逐渐升级.注意锁可以升

  • Java synchronized与死锁深入探究

    目录 1.synchronized的特性 2.synchronized使用示例: 3.Java标准库中的线程安全类 4.死锁是什么 5.如果避免死锁 1.synchronized的特性 1). 互斥性 当某个线程执行到 synchronized 所修饰的对象时 , 该线程对象会加锁(lock) , 其他线程如果执行到同一个对象的 synchronized 就会产生阻塞等待. 进入 synchronized 修饰的代码块 , 相当于加锁. 退出 synchronized 修饰着代码块 , 相当于解

  • Java必会的Synchronized底层原理剖析

    目录 1. synchronized作用 2. synchronized用法 3. synchronized加锁原理 synchronized作为Java程序员最常用同步工具,很多人却对它的用法和实现原理一知半解,以至于还有不少人认为synchronized是重量级锁,性能较差,尽量少用. 但不可否认的是synchronized依然是并发首选工具,连volatile.CAS.ReentrantLock都无法动摇synchronized的地位.synchronized是工作面试中的必备技能,今天就

  • java同步锁的正确使用方法(必看篇)

    同步锁分类 对象锁(this) 类锁(类的字节码文件对象即类名.class) 字符串锁(比较特别) 应用场景 在多线程下对共享资源的安全操作. 需求:启动5个线程对共享资源total进行安全操作. 同步锁在多线程单例模式下的使用 以上三类同步锁都可以. package cn.myThread; public class MyThread implements Runnable { private static int total = 10; @Override public void run()

  • 一个例子带你看懂Java中synchronized关键字到底怎么用

    目录 前言 使用同步方法 使用同步语句或块 什么是同步? 为啥需要同步? 总结 前言 在平时开发中,synchronized关键字经常遇到,你知道synchronized怎么用吗?本文给大家介绍一下. 我们有两种方法使用同步: 使用同步方法 使用同步语句或块 使用同步方法 要使方法同步,只需将synchronized关键字添加到其声明中: public class SynchronizedDemo { private int i = 0; public synchronized void add

随机推荐