JAVA 同步实现原理
Synchronized的基本使用
Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题。
从语法上讲,Synchronized总共有三种用法:
- 修饰普通方法
- 修饰静态方法
- 修饰代码块
接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,其他基本保持一致)。
- 没有同步的情况
package com.paddx.test.concurrent;
public class SynchronizedTest {
public void method1(){
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public void method2(){
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
执行结果如下,线程1和线程2同时进入执行状态,线程2执行速度比线程1快,所以线程2先执行完成,这个过程中线程1和线程2是同时执行的。
Method 1 start
Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end
- 对普通方法同步
package com.paddx.test.concurrent;
public class SynchronizedTest {
public synchronized void method1(){
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public synchronized void method2(){
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
执行结果如下,跟代码段一比较,可以很明显的看出,线程2需要等待线程1的method1执行完成才能开始执行method2方法。
Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
- 静态方法(类)同步
package com.paddx.test.concurrent;
public class SynchronizedTest {
public static synchronized void method1(){
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public static synchronized void method2(){
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
final SynchronizedTest test2 = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method2();
}
}).start();
}
}
执行结果如下,对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。
Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
- 代码块同步
package com.paddx.test.concurrent;
public class SynchronizedTest {
public void method1(){
System.out.println("Method 1 start");
try {
synchronized (this) {
System.out.println("Method 1 execute");
Thread.sleep(3000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public void method2(){
System.out.println("Method 2 start");
try {
synchronized (this) {
System.out.println("Method 2 execute");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
执行结果如下,虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成。
Method 1 start
Method 1 execute
Method 2 start
Method 1 end
Method 2 execute
Method 2 end
Synchronized 原理
先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的
package com.paddx.test.concurrent;
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
反编译结果
![](https://img2018.cnblogs.com/blog/1504257/201810/1504257-20181007101318184-1233354365.jpg)
关于这两条指令的作用,我们直接参考JVM规范中描述: ` Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count. • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership. `
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
- 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
- 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
- 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit: ` The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref. The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.`
- 执行monitorexit的线程必须是objectref所对应的monitor的所有者。
- 指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
通过这两段描述,我们应该能很清楚的看出 Synchronized的实现原理
Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
我们再来看一下同步方法的反编译结果:
package com.paddx.test.concurrent;
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
反编译结果
![](https://img2018.cnblogs.com/blog/1504257/201810/1504257-20181007101329884-483712820.jpg)
从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
运行结果解释
有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。
- 代码段2结果: 虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。
- 代码段3结果: 虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。
- 代码段4结果: 对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。
总结
Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到内核态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。
因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“偏向锁”和“轻量级锁”。无锁 --> 偏向锁 --> 轻量级 --> 重量级
原文地址:http://awingger.com/2017/10/25/JAVA%E5%90%8C%E6%AD%A5%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/
JAVA 同步实现原理的更多相关文章
- Java 同步代码块 - Synchronized Blocks
java锁实现原理: http://blog.csdn.net/endlu/article/details/51249156 The synchronized keyword can be used ...
- Java中多线程原理详解
Java是少数的集中支持多线程的语言之一,大多数的语言智能运行单独的一个程序块,无法同时运行不同的多个程序块,Java的多线程机制弥补了这个缺憾,它可以让不同的程序块一起运行,这样可以让程序运行更加顺 ...
- Java HashMap工作原理及实现
Java HashMap工作原理及实现 2016/03/20 | 分类: 基础技术 | 0 条评论 | 标签: HASHMAP 分享到:3 原文出处: Yikun 1. 概述 从本文你可以学习到: 什 ...
- 全面解读Java NIO工作原理(4)
全面解读Java NIO工作原理(4) 2011-12-14 10:31 Rollen Holt Rollen Holt的博客 我要评论(0) 字号:T | T JDK 1.4 中引入的新输入输出 ( ...
- java同步和互斥【用具体程序说明】
java同步和互斥[用具体程序说明] 所有对象都自动含有单一的锁,也就是所有对象都有且只有唯一的锁,所以当某个任务(线程)访问一个类A中含有sycnhronized的方法是,那么 ...
- dubbo_远程同步调用原理
Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况. Dubbo缺省协议,使用基于mina1.1.7+hessian3. ...
- Java LinkedHashMap工作原理及实现
Java LinkedHashMap工作原理及实现 原文出处: Yikun 1. 概述 在理解了#7 介绍的HashMap后,我们来学习LinkedHashMap的工作原理及实现.首先还是类似的,我们 ...
- 10分钟看懂, Java NIO 底层原理
目录 写在前面 1.1. Java IO读写原理 1.1.1. 内核缓冲与进程缓冲区 1.1.2. java IO读写的底层流程 1.2. 四种主要的IO模型 1.3. 同步阻塞IO(Blocking ...
- 死磕 java同步系列之AQS起篇
问题 (1)AQS是什么? (2)AQS的定位? (3)AQS的实现原理? (4)基于AQS实现自己的锁? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为Jav ...
随机推荐
- @Inherited:允许子类继承父类的注解。
在看定义注解的相关文章的时候,看到这个@Inherited注解,简单的说明并没有真正搞懂是什么意思.在网上搜索了一些相关的内容,现在把一篇文章转载过来.以便后面使用. 文章出处,转载地址:(http: ...
- JavaScript跨域资源请求(CORS)解决方案
跨域:当协议.主域名.子域名.端口号中任意一个不相同时都不算同一个域,而在不同域之间请求数据即为跨域请求.解决方法有以下几种(如有错误欢迎指出)以请求图片url为例: 1.通过XMLHttpReque ...
- 030 Substring with Concatenation of All Words 与所有单词相关联的字串
给定一个字符串 s 和一些长度相同的单词 words,找出 s 与 words 中所有单词(words 每个单词只出现一次)串联一起(words中组成串联串的单词的顺序随意)的字符串匹配的所有起始索引 ...
- POI 博客总结.....
主键类: HSSFRow row1= sheet.createRow(1); row.createCell(0).setCellValue("学生编号"); row.createC ...
- H5移动端原生长按事件
// 函数名longpress// 参数为: 需长按元素的id.长按之后处理函数func function longPress(id, func,timeout=500) { var timeOutE ...
- Android数据库(1)、SQLite数据库介绍
一.关系性数据库 关系型数据库主要有以下三个特征,尤为明显,如果没有这个三个特征约束,当多个客户端使用数据的时候就会出现各种各样的错误,所以关系型数据库定义这些约束,让客户端程序只要遵守这个规则便 ...
- 使用data-自定义数据及如何获取该值
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- fish 常用主题推荐
在安装fish的前提下 omf install zish omf theme zish zish
- 百倍性能的PL/SQL优化案例(r11笔记第13天)
我相信你是被百倍性能的字样吸引了,不过我所想侧重的是优化的思路,这个比优化技巧更重要,而结果嘛,其实我不希望说成是百倍提升,“”自黑“”一下. 有一个真实想法和大家讨论一下,就是一个SQL语句如果原本 ...
- RF的一些技术知识
1. dBm 定义的是 miliwatt(毫瓦特).0 dBm=1mw: dBw 定义 watt.0 dBW = 1 W =1000 mw = 10lg(1000/1)dBm=30dbm. dB ...