深入理解synchronized关键字
2019独角兽企业重金招聘Python工程师标准>>>
synchronized是并发编程中重要的使用工具之一,我们必须学会使用并且掌握它的原理。
概念及作用
JVM自带的关键字,可在需要线程安全的业务场景中使用,来保证线程安全。
用法
按照锁的对象区分可以分为对象锁和类锁按照在代码中的位置区分可以分为方法形式和代码块形式
对象锁
锁对象为当前this或者说是当前类的实例对象
public void synchronized method() {
System.out.println("我是普通方法形式的对象锁");
}
public void method() {
synchronized(this) {
System.out.println("我是代码块形式的对象锁");
}
}
类锁
锁的是当前类或者指定类的Class对象。一个类可能有多个实例对象,但它只可能有一个Class对象。
public static void synchronized method() {
System.out.println("我是静态方法形式的类锁");
}
public void method() {
synchronized(*.class) {
System.out.println("我是代码块形式的类锁");
}
}
SimpleExample
最基本的用法在上一个标题用法中已将伪代码列出,这里列举在以上基础上稍微变化一些的用法
多个实例,对当前实例加锁,同步执行,对当前类Class对象加锁,异步执行。
public class SimpleExample implements Runnable {
static SimpleExample instance1 = new SimpleExample();
static SimpleExample instance2 = new SimpleExample();
@Override
public void run() {
method1();
method2();
method3();
method4();
}
public synchronized void method1() {
common();
}
public static synchronized void method2() {
commonStatic();
}
public void method3() {
synchronized(this) {
common();
}
}
public void method4() {
synchronized(MultiInstance.class) {
common();
}
}
public void method5() {
common();
}
public void method6() {
commonStatic();
}
public void common() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 执行完毕");
}
public static void commonStatic() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 执行完毕");
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("finished");
}
}
method1()、method3()结果为:
线程 Thread-0 正在执行
线程 Thread-1 正在执行
线程 Thread-0 执行完毕
线程 Thread-1 执行完毕
finished
method2()、method4()执行结果为:
线程 Thread-0 正在执行
线程 Thread-0 执行完毕
线程 Thread-1 正在执行
线程 Thread-1 执行完毕
finished
对象锁和类锁,锁的对象不一样,互不影响,所以异步执行
// 将run方法改为
@Override
public void run() {
if("Thread-0".equals(Thread.currentThread().getName())) {
method1();
} else {
method2();
}
}
// 将main方法改为
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("finished");
}
结果为:
线程 Thread-0 正在执行
线程 Thread-1 正在执行
线程 Thread-1 执行完毕
线程 Thread-0 执行完毕
finished
3.对象锁和无锁得普通方法,普通方法不需要持有锁,所以异步执行
// 将run方法改为
@Override
public void run() {
if("Thread-0".equals(Thread.currentThread().getName())) {
method1();
} else {
method5();
}
}
// main方法同 2
结果为:
线程 Thread-0 正在执行
线程 Thread-1 正在执行
线程 Thread-0 执行完毕
线程 Thread-1 执行完毕
finished
类锁和无锁静态方法,异步执行
// 将run方法改为
@Override
public void run() {
if("Thread-0".equals(Thread.currentThread().getName())) {
method1();
} else {
method6();
}
}
// main方法同 2
结果为:
线程 Thread-0 正在执行
线程 Thread-1 正在执行
线程 Thread-0 执行完毕
线程 Thread-1 执行完毕
finished
方法抛出异常,synchronized锁自动释放
// run方法改为
@Override
public void run() {
if ("Thread-0".equals(Thread.currentThread().getName())) {
method7();
} else {
method8();
}
}
public synchronized void method7() {
try {
...
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void method8() {
common();
}
public static void main(String[] args) throws InterruptedException {
// 同 2
}
结果为:
线程 Thread-0 正在执行
java.lang.Exception
at com.marksman.theory2practicehighconcurrency.synchronizedtest.blog.SynchronizedException.method7(SynchronizedException.java:26)
at com.marksman.theory2practicehighconcurrency.synchronizedtest.blog.SynchronizedException.run(SynchronizedException.java:15)
at java.lang.Thread.run(Thread.java:748)
线程 Thread-0 执行结束
线程 Thread-1 正在执行
线程 Thread-1 执行结束
finished
// 这说明抛出异常后持有对象锁的method7()方法释放了锁,这样method8()才能获取到锁并执行。
6.可重入特性
public class SynchronizedRecursion {
int a = 0;
int b = 0;
private void method1() {
System.out.println("method1正在执行,a = " + a);
if (a == 0) {
a ++;
method1();
}
System.out.println("method1执行结束,a = " + a);
}
private synchronized void method2() {
System.out.println("method2正在执行,b = " + b);
if (b == 0) {
b ++;
method2();
}
System.out.println("method2执行结束,b = " + b);
}
public static void main(String[] args) {
SynchronizedRecursion synchronizedRecursion = new SynchronizedRecursion();
synchronizedRecursion.method1();
synchronizedRecursion.method2();
}
}
结果为:
method1正在执行,a = 0
method1正在执行,a = 1
method1执行结束,a = 1
method1执行结束,a = 1
method2正在执行,b = 0
method2正在执行,b = 1
method2执行结束,b = 1
method2执行结束,b = 1
可以看到method1()与method2()的执行结果一样的,method2()在获取到对象锁以后,在递归调用时不需要等上一次调用先释放后再获取,而是直接进入,这说明了synchronized的可重入性。
当然,除了递归调用,调用同类的其它同步方法,调用父类同步方法,都是可重入的,前提是同一对象去调用,这里就不一一列举了.
总结一下
一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待;
每个实例都对应有自己的一把锁,不同实例之间互不影响;
锁对象是*.class以及synchronized修饰的static方法时,所有对象共用一把类锁;
无论是方法正常执行完毕或者方法抛出异常,都会释放锁;
使用synchronized修饰的方法都是可重入的。
synchronized的实现原理
monitorenter和monitorexit
将下面两段代码分别用 javac *.java编译成.class文件,再反编译 javap -verbose *.class文件
public class SynchronizedThis {
public void method() {
synchronized(this) {}
}
}
// 反编译结果
public void method();
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
public class SynchronizedMethod {
public synchronized void method() {}
}
// 反编译结果
public synchronized void method();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 2: 0
可以看到:
synchronized加在代码块上,JVM是通过monitorenter和monitorexit来控制锁的获取的释放的;
synchronized加在方法上,JVM是通过ACC_SYNCHRONIZED来控制的,但本质上也是通过monitorenter和monitorexit指令控制的。
对象头
上面我们提到monitor,这是什么鬼? 其实,对象在内存是这样存储的,包括对象头、实例数据和对齐填充padding,其中对象头包括Mark Word和类型指针。
Mark Word
Mark Word用于存储对象自身的运行时数据,如哈希码(identity_hashcode)、GC分代年龄(age)、锁状态标志(lock)、线程持有的锁、偏向线程ID(thread)、偏向时间戳(epoch)等等,占用内存大小与虚拟机位长一致。
Mark Word (32 bits) | State 锁状态 |
---|---|
identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal 无锁 |
thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased 偏向锁 |
ptr_to_lock_record:30 | lock:2 | Lightweight Locked 轻量级锁 |
ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked 重量级锁 |
| lock:2 | Marked for GC GC标记 |
Mark Word (64 bits) | State 锁状态 |
---|---|
unused:25|identity_hashcode:31|unused:1|age:4|biased_lock:1|lock:2 | Normal 无锁 |
thread:54 |epoch:2|unused:1|age:4|biased_lock:1|lock:2 | Biased 偏向锁 |
ptr_to_lock_record:62 | lock:2 | Lightweight Locked 轻量级锁 |
ptr_to_heavyweight_monitor:62 | lock:2 | Heavyweight Locked 重量级锁 |
| lock:2 | Marked for GC GC标记 |
可以看到,monitor就存在Mark Word中。
类型指针
类型指针指向对象的类元数据metadata,虚拟机通过这个指针确定该对象是哪个类的实例。
锁状态
biased_lock | lock | 状态 |
---|---|---|
0 | 01 | 无锁 |
1 | 01 | 偏向锁 |
0 | 00 | 轻量级锁 |
0 | 10 | 重量级锁 |
0 | 11 | GC标记 |
JDK对synchronized的优化
jdk1.6之前synchronized是很重的,所以并不被开发者偏爱,随着后续版本jdk对synchronized的优化使其越来越轻量,它还是很好用的,甚至ConcurrentHashMap在jdk的put方法都在jdk1.8时从ReetrantLock.tryLock()改为用synchronized来实现同步。 并且还引入了偏向锁,轻量级锁等概念。
偏向锁 baised_lock
如果一个线程获取了偏向锁,那么如果在接下来的一段时间里,如果没有其他线程来抢占锁,那么获取锁的线程在下一次进入方法时不需要重新获取锁。
synchronized与ReentrantLock的区别
区别 | synchronized | ReentrantLock |
---|---|---|
灵活性 | 代码简单,自动获取、释放锁 | 相对繁琐,需要手动获取、释放锁 |
是否可重入 | 是 | 是 |
作用位置 | 可作用在方法和代码块 | 只能用在代码块 |
获取、释放锁的方式 | monitorenter、monitorexit、ACC_SYNCHRONIZED | 尝试非阻塞获取锁tryLock()、超时获取锁tryLock(long timeout,TimeUnit unit)、unlock() |
获取锁的结果 | 不知道 | 可知,tryLock()返回boolean |
使用注意事项 | 1、锁对象不能为空(锁保存在对象头中,null没有对象头)2、作用域不宜过大 |
1、切记要在finally中unlock(),否则会形成死锁 2、不要将获取锁的过程写在try块内,因为如果在获取锁时发生了异常,异常抛出的同时,也会导致锁无故被释放。 |
扩展阅读
死磕 Java 并发:深入分析 synchronized 的实现原理
深入了解Java之虚拟机内存
深入理解正则表达式
Java并发编程之volatile关键字解析
缓存在高并发场景下的常见问题
转载于:https://my.oschina.net/javafirst/blog/3024676
深入理解synchronized关键字相关推荐
- synchronized()_这篇文章带你彻底理解synchronized关键字
Synchronized关键字一直是工作和面试中的重点.这篇文章准备彻彻底底的从基础使用到原理缺陷等各个方面来一个分析,这篇文章由于篇幅比较长,但是如果你有时间和耐心,相信会有一个比较大的收获,所以, ...
- Java中 synchronized 关键字的理解
synchronized 关键字的理解 在Java中,synchronized 是一个重量级的控制并发的关键字. 这个关键字可以保证并发过程所必须的"原子性","可见性& ...
- 从分布式锁角度理解Java的synchronized关键字
分布式锁 分布式锁就以zookeeper为例,zookeeper是一个分布式系统的协调器,我们将其理解为一个文件系统,可以在zookeeper服务器中创建或删除文件夹或文件.设D为一个数据系统,不具备 ...
- synchronized关键字理解
引入 需求 先看一个简单的需求 我们现在模拟一下银行的叫号机生产号码(号码范围为1~100),假设我们现在有四个取号机,要求每个人取得到号码不重复,并且不能有遗漏,很多人就很快的可以写出下面的代码 代 ...
- java synchronized_Java中synchronized关键字理解
好记性不如烂笔头~~ 并发编程中synchronized关键字的地位很重要,很多人都称它为重量级锁.利用synchronized实现同步的基础:Java中每一个对象都可以作为锁.具体表现为以下三种形式 ...
- Java线程同步机制synchronized关键字的理解
由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 需要明确的几个问题: ...
- Java 并发编程中使用 ReentrantLock 替代 synchronized 关键字原语
标签: Java 5 引入的 Concurrent 并发库软件包中,提供了 ReentrantLock 可重入同步锁,用来替代 synchronized 关键字原语,并可提供更好的性能,以及更强大的功 ...
- synchronized关键字以及实例锁 类锁
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...
- synchronized不能锁静态变量_多线程编程不可错过——彻底理解synchronized
持续分享互联网研发技术,欢迎关注我.本人是一线架构师,有问题可以沟通. 1. synchronized简介 在学习知识前,我们先来看一个现象: public class SynchronizedDem ...
最新文章
- HDU 2830 Matrix Swapping II
- opencv python是什么_Python+OpenCV 十几行代码模仿世界名画
- 【python】点分十进制ip与数字互转
- websocket趣说_转
- 博客积分规则 博客等级
- oracle查询表实际大小,简要分析估算oracle表的大小
- iOS开发--一些UITabBarItem属性的设置[转]
- mysql定时清空表数据_Mysql实现定时清空一张表的旧数据并保留几条数据
- 利用Python爬取网易上证所有股票数据(代码
- 【C语言笔记进阶篇】第二章:字符串函数和内存函数
- 基于R实现统计中的检验方法---卡方检验
- 一次搞定this和闭包
- 2022 年第十二届 MathorCup 高校数学建模挑战赛C题解析
- 金仓数据库KingbaseES V8R3使用Gorm指南
- SEO独家揭秘:影响网站降权被K的七项因素
- 如何解决3G模块和USB转串口冲突问题
- html页面中汉字上面显示拼音
- Collections 的 emptyList()、emptyMap() 、emptySet()
- 10分钟快速搭建实战Web项目:生鲜电商
- 为什么游戏更新不了服务器维护,自走棋手游更新不了怎么办 更新失败解决方法介绍...