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 的所有权。

 public class SynchronizedMethod {
public synchronized void methodA() {
System.out.println("MethodA start"); }
}

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

 Compiled from "SynchronizedTest.java"
public class com.memory.SynchronizedTest {
public com.memory.SynchronizedTest();
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: ldc #3 // String MethodA start
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
}

锁的种类

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(下)的更多相关文章

  1. 内置锁(二)synchronized下的等待通知机制

    一.等待/通知机制的简介 线程之间的协作:   为了完成某个任务,线程之间需要进行协作,采取的方式:中断.互斥,以及互斥上面的线程的挂起.唤醒:如:生成者--消费者模式.或者某个动作完成,可以唤醒下一 ...

  2. synchronized下的 i+=2 和 i++ i++执行结果居然不一样

    起因 逛[博客园-博问]时发现了一段有意思的问题: 问题链接:https://q.cnblogs.com/q/140032/ 这段代码是这样的: import java.util.concurrent ...

  3. 深入浅出Java并发包—锁机制(一)

    前面我们看到了Lock和synchronized都能正常的保证数据的一致性(上文例子中执行的结果都是20000000),也看到了Lock的优势,那究竟他们是什么原理来保障的呢?今天我们就来探讨下Jav ...

  4. Java并发知识整理

    整理了一下前段时间学习Java并发的笔记,大约有40篇. 1. Java并发基础知识 并发基础(一) 线程介绍 并发基础(二) Thread类的API总结 并发基础(三) java线程优先级 并发基础 ...

  5. Java 知识笔记 - 类、集合、多线程、IO、JVM(最后一次更新,2019年02月17日)

    目录 Class 内部类.静态内部类.匿名内部类.局部内部类 Collection Java Collection Set Queue Map Collections Arrays System Co ...

  6. CAS及其ABA问题

    CAS.volatile是JUC包实现同步的基础.Synchronized下的偏向锁.轻量级锁的获取.释放,lock机制下锁的获取.释放,获取失败后线程的入队等操作都是CAS操作锁标志位.state. ...

  7. Java-线程等待、唤醒与中断

    一.sleep() 与 wait() 两者都会让当前线程进入等待状态.唤醒后都需要等待 CPU 资源,不一定会立即执行.若在等待期间被调用此线程的的 interrupt() 方法,将会产生 Inter ...

  8. JUC——检视阅读

    JUC--检视阅读 参考资料 JUC知识图参考 JUC框架学习顺序参考 J.U.C学习总结参考,简洁直观 易百并发编程,实践操作1,不推荐阅读,不及格 JUC文章,带例子讲解,可以学习2 Doug L ...

  9. C++程序结构---1

    C++ 基础教程Beta 版 原作:Juan Soulié 翻译:Jing Xu (aqua) 英文原版 本教程根据Juan Soulie的英文版C++教程翻译并改编. 本版为最新校对版,尚未定稿.如 ...

  10. 并发情况下synchronized死锁

    存在缺陷的代码: public class DataPropertyIdAndNameRepositoryImpl{ /** 发布标志 */ private volatile boolean publ ...

随机推荐

  1. python去掉行尾的换行符

    python去掉行尾的换行符 mystring.strip().replace(' ', '').replace('\n', '').replace('\t', '').replace('\r', ' ...

  2. js限制输入内容

    1,限制输入正整数 onkeyup="value=value.replace(/[^\d]/g,'')" 2,限制输入为数字,包括小数(有瑕疵) onkeyup="val ...

  3. 斯特林公式求N!

        n!的长度为 ll ans = log10(2*pi*n)/2 + n*(log10(n/exp(1.0)))+1;  

  4. JMeter设置Http代理对web或者app进行录制

    一.录制web 1.首先保证JMeter的安装环境都正确.启动JMeter:在安装路径的bin目录下双击jmeter.bat (例如:D:\apache-jmeter-2.13\bin) ​2.打开J ...

  5. review10

    public int indexOf(String s); 字符串的索引位置时从0开始的(只有一个参数时),从当前字符串的头开始检索字符串s,并返回首次出现s的索引位置.如果没有检测到字符串s,该方法 ...

  6. 十大监视SQL Server性能的计数器

      作为DBA,每个人都会用一系列计数器来监视SQLSERVER的运行环境,使用计数器,既可以衡量当前的数据库的性能,还可以和以前的性能进行对比.我们也可以一直以快速和简单的方法把计数器做了一张图表来 ...

  7. Linux系统memcached安装

    [memcached安装] Linux系统安装memcached可以自动安装,也可以手动编译安装,这里使用手动编译安装 1.Linux系统安装memcached,首先要先安装libevent库. 下载 ...

  8. 改善Chrome在Windows下的中文字体效果

    方法一:摘自卡饭论坛 chrome://flags/#disable-direct-write 启用 chrome://flags/#num-raster-threads 改为 1 chrome:// ...

  9. APP登录的机制

    1.APP每次发送请求时,都会发送header给服务器,服务器去校验传过来的信息是否正确:校验成功后登录成功,若传入的信息不符合该用户的信息则服务器判断,传给APP登录失败 每次的请求都会传入上图中的 ...

  10. flash、js 函数 互相调用

    js调用flex函数 flex 利用ExternalInterface.addCallback(“注册的方法名”,As中的函数名)进行注册 js中,用document.getElementById(“ ...