synchronized到底锁住的是谁?
本文代码仓库:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sync
先来一道校招级并发编程笔试题
题目:利用5个线程并发执行,num数字累计计数到10000,并打印。
/**
* Description:
* 利用5个线程并发执行,num数字累加计数到10000,并打印。
* 2019-06-13
* Created with OKevin.
*/
public class Count {
private int num = 0; public static void main(String[] args) throws InterruptedException {
Count count = new Count(); Thread thread1 = new Thread(count.new MyThread());
Thread thread2 = new Thread(count.new MyThread());
Thread thread3 = new Thread(count.new MyThread());
Thread thread4 = new Thread(count.new MyThread());
Thread thread5 = new Thread(count.new MyThread());
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
thread5.join(); System.out.println(count.num); } private synchronized void increse() {
for (int i = 0; i < 2000; i++) {
num++;
}
} class MyThread implements Runnable {
@Override
public void run() {
increse();
}
}
}
这道校招级的并发编程面试题,题目不难,方法简单。其中涉及一个核心知识点——synchronized(当然这题的解法有很多),这也是本文想要弄清的主题。
synchronized被大大小小的程序员广泛使用,有的程序员偷懒,在要求保证线程安全时,不加思索的就在方法前加入了synchronized关键字(例如我刚才那道校招级大题)。偷懒归偷懒,CodeReview总是要进行的,面对同事的“指责”,要求优化这个方法,将synchronized使用同步代码块的方式提高效率。
synchronized要按照同步代码块来保证线程安全,这可就加在方法“复杂”多了。有:synchronized(this){}这么写的,也有synchronized(Count.class){}这么写的,还有定义了一个private Object obj = new Object; ….synchronized(obj){}这么写的。此时不禁在心里“W*F”。
synchronized你到底锁住的是谁?
synchronized从语法的维度一共有3个用法:
静态方法加上关键字
实例方法(也就是普通方法)加上关键字
方法中使用同步代码块
前两种方式最为偷懒,第三种方式比前两种性能要好。
synchronized从锁的是谁的维度一共有两种情况:
锁住类
锁住对象实例
我们还是从直观的语法结构上来讲述synchronized。
1)静态方法上的锁
静态方法是属于“类”,不属于某个实例,是所有对象实例所共享的方法。也就是说如果在静态方法上加入synchronized,那么它获取的就是这个类的锁,锁住的就是这个类。
2)实例方法(普通方法)上的锁
实例方法并不是类所独有的,每个对象实例独立拥有它,它并不被对象实例所共享。这也比较能推出,在实例方法上加入synchronized,那么它获取的就是这个累的锁,锁住的就是这个对象实例。
那锁住类还是锁住对象实例,这跟我线程安全关系大吗?大,差之毫厘谬以千里的大。为了更好的理解锁住类还是锁住对象实例,在进入“3)方法中使用同步代码块”前,先直观的感受下这两者的区别。
对实例方法(普通方法)上加关键字锁住对象实例锁的解释
首先定义一个Demo类,其中的实例方法加上了synchronized关键字,按照所述也就是说锁住的对象实例。
/**
* Description:
* 死循环,目的是两个线程抢占一个锁时,只要其中一个线程获取,另一个线程就会一直阻塞
* 2019-06-13
* Created with OKevin.
*/
public class Demo { public synchronized void demo() {
while (true) { //synchronized方法内部是一个死循环,一旦一个线程持有过后就不会释放这个锁
System.out.println(Thread.currentThread());
}
}
}
可以看到在demo方法中定义了一个死循环,一旦一个线程持有这个锁后其他线程就不可能获取这个锁。结合上述synchronized修饰实例方法锁住的是对象实例,如果两个线程针对的是一个对象实例,那么其中一个线程必然不可能获取这个锁;如果两个线程针对的是两个对象实例,那么这两个线程不相关均能获取这个锁。
自定义线程,调用demo方法。
/**
* Description:
* 自定义线程
* 2019-06-13
* Created with OKevin.
*/
public class MyThread implements Runnable {
private Demo demo; public MyThread(Demo demo) {
this.demo = demo;
} @Override
public void run() {
demo.demo();
}
}
测试程序1:两个线程抢占一个对象实例的锁
/**
* Description:
* 两个线程抢占一个对象实例的锁
* 2019-06-13
* Created with OKevin.
*/
public class Main1 {
public static void main(String[] args) {
Demo demo = new Demo();
Thread thread1 = new Thread(new MyThread(demo));
Thread thread2 = new Thread(new MyThread(demo));
thread1.start();
thread2.start();
}
}
如上图所示,输出结果显然只会打印一个线程的信息,另一个线程永远也获取不到这个锁。
测试程序2:两个线程分别抢占两个对象实例的锁
/**
* Description:
* 两个线程分别抢占两个对象实例的锁
* 2019-06-13
* Created with OKevin.
*/
public class Main2 {
public static void main(String[] args) {
Demo demo1 = new Demo();
Demo demo2 = new Demo();
Thread thread1 = new Thread(new MyThread(demo1));
Thread thread2 = new Thread(new MyThread(demo2));
thread1.start();
thread2.start();
}
}
如上图所示,显然,两个线程均进入到了demo方法,也就是均获取到了锁,证明,两个线程抢占的就不是同一个锁,这就是synchronized修饰实例方法时,锁住的是对象实例的解释。
对静态方法上加关键字锁住类锁的解释
静态方法是类所有对象实例所共享的,无论定义多少个实例,是要是静态方法上的锁,它至始至终只有1个。将上面的程序Demo中的方法加上static,无论使用“测试程序1”还是“测试程序2”,均只有一个线程可以抢占到锁,另一个线程仍然是永远无法获取到锁。
让我们重新回到从语法结构上解释synchronized。
3)方法中使用同步代码块
程序的改良优化需要建立在有坚实的基础,如果在不了解其内部机制,改良也仅仅是“形式主义”。
结合开始CodeReview的例子:
你的同事在CodeReview时,要求你将实例方法上的synchronized,改为效率更高的同步代码块方式。在你不清楚同步代码的用法时,网上搜到了一段synchronized(this){}代码,复制下来发现也能用,此时你以为你改良优化了代码。但实际上,你可能只是做了一点形式主义上的优化。
为什么这么说?这需要清楚地认识同步代码块到底应该怎么用。
3.1)synchronized(this){...}
this关键字所代表的意思是该对象实例,换句话说,这种用法synchronized锁住的仍然是对象实例,他和public synchronized void demo(){}可以说仅仅是做了语法上的改变。
/**
* 2019-06-13
* Created with OKevin.
**/
public class Demo { public synchronized void demo1() {
while (true) { //死循环目的是为了让线程一直持有该锁
System.out.println(Thread.currentThread());
}
} public synchronized void demo2() {
while (true) {
System.out.println(Thread.currentThread());
}
}
}
改为以下方式:
/**
* Description:
* synchronized同步代码块对本实例加锁(this)
* 假设demo1与demo2方法不相关,此时两个线程对同一个对象实例分别调用demo1与demo2,只要其中一个线程获取到了锁即执行了demo1或者demo2,此时另一个线程会永远处于阻塞状态
* 2019-06-13
* Created with OKevin.
*/
public class Demo { public void demo1() {
synchronized (this) {
while (true) { //死循环目的是为了让线程一直持有该锁
System.out.println(Thread.currentThread());
}
}
} public void demo2() {
synchronized (this) {
while (true) {
System.out.println(Thread.currentThread());
}
}
}
}
也许后者在JVM中可能会做一些特殊的优化,但从代码分析上来讲,两者并没有做到很大的优化,线程1执行demo1,线程2执行demo2,由于两个方法均是抢占对象实例的锁,只要有一个线程获取到锁,另外一个线程只能阻塞等待,即使两个方法不相关。
3.2)private Object obj = new Object(); synchronized(obj){...}
/**
* Description:
* synchronized同步代码块对对象内部的实例加锁
* 假设demo1与demo2方法不相关,此时两个线程对同一个对象实例分别调用demo1与demo2,均能获取各自的锁
* 2019-06-13
* Created with OKevin.
*/
public class Demo {
private Object lock1 = new Object();
private Object lock2 = new Object(); public void demo1() {
synchronized (lock1) {
while (true) { //死循环目的是为了让线程一直持有该锁
System.out.println(Thread.currentThread());
}
}
} public void demo2() {
synchronized (lock2) {
while (true) {
System.out.println(Thread.currentThread());
}
}
}
}
经过上面的分析,看到这里,你可能会开始懂了,可以看到demo1方法中的同步代码块锁住的是lock1对象实例,demo2方法中的同步代码块锁住的是lock2对象实例。如果线程1执行demo1,线程2执行demo2,由于两个方法抢占的是不同的对象实例锁,也就是说两个线程均能获取到锁执行各自的方法(当然前提是两个方法互不相关,才不会出现逻辑错误)。
3.3)synchronized(Demo.class){...}
这种形式等同于抢占获取类锁,这种方式,同样和3.1一样,收效甚微。
所以CodeReivew后的代码应该是3.2) private Object obj = new Object(); synchronized(obj){...},这才是对你代码的改良优化。
本文代码仓库:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sync
关注公众号:coderbuff,下期预告:synchronized凭什么锁得住?
这是一个能给程序员加buff的公众号 (CoderBuff)
synchronized到底锁住的是谁?的更多相关文章
- Java synchronized到底锁住的是什么?
使用环境:多线程java程序中. 作用:在多线程的环境下,控制synchronized代码段不被多个线程同时执行.synchronized既可以加在一段代码上,也可以加在方法上. 使用:synchro ...
- Java同步方法:synchronized到底锁住了谁?
目录 前言 同步方法 类的成员方法 类的静态方法 同步代码块 总结 其他同步方法 参考资料 前言 相信不少同学在上完Java课后,对于线程同步部分的实战,都会感到不知其然. 比如上课做实验的时候,按着 ...
- Java synchronized(this)锁住的是什么
synchronized锁住的是括号里面的对象,而不是代码. 对于非static的synchronized方法,锁的就是对象本身,也就是this.
- java synchronized究竟锁住的是什么
刚学java的时候,仅仅知道synchronized一个线程锁.能够锁住代码,可是它真的能像我想的那样,能够锁住代码吗? 在讨论之前先看一下项目中常见关于synchronized的使用方法: publ ...
- 关于synchronized无法锁住Integer原因
原因 在多线程的时候,为了保证数据安全,必须在修改数据时使用线程同步,java中的synchronized用来实现线程同步.线程列队. 学完多线程基础的我,写一个多线程交替输出1,2,3,4,5... ...
- 关于Synchronized关键字锁住对象的嵌套问题
如果在子关键字代码块中调用了sleep,是否会保留有所的锁?
- Java并发,synchronized锁住的内容
synchronized用在方法上锁住的是什么? 锁住的是当前对象的当前方法,会使得其他线程访问该对象的synchronized方法或者代码块阻塞,但并不会阻塞非synchronized方法. 脏读 ...
- synchronized锁住的是代码还是对象
不同的对象 public class Sync { public synchronized void test() { System.out.println("test start" ...
- Java线程同步:synchronized锁住的是代码还是对象
所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步.这叫减小锁的粒度,使代码更大程度的并发.原因是基于以上的思想,锁的代码段太长 ...
随机推荐
- Nginx专题(2):Nginx的负载均衡策略及其配置
本文介绍了Nginx的负载均衡策略,一致性hash分配原理,及常用的故障节点的摘除与恢复配置. 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第一期-宜信支付结算八方数据团队高级技术经理 ...
- luogu P4981 父子
题目背景 上演在各大学男生寝室的日常 :: A :A: "我没带纸,快来厕所救我!" B :B: "叫爸爸." A :A: "爸爸!" .. ...
- kafka - java.nio.file.FileSystemException
在启动Kafka时报错无法启动 E:\kafka_2.12-2.3.1\kafka-logs\__consumer_offsets-48\00000000000000000000.timeindex. ...
- 用jquery实现楼层滚动对应导航高亮
html 结构排版: // 定位到页面左侧或者右侧 <div class="nav"> <ul id="menu-list"& ...
- ThreadLocal的进化——InheritableThreadLocal
之前有介绍过 ThreadLocal,JDK 后来针对此做了一个升级版本 InheritableThreadLocal,今天就来好好介绍下. 为什么要升级 首先我们来想想,为什么要升级?这就要说起 T ...
- rsync服务的讲解
第2章 rsync备份服务器的搭建 2.1 rsync备份服务器的概念 2.1.1 概念 rsync服务器对网站服务器数据进行备份(防止数据丢失和数据进行恢复) rsync服务器对网站服务器数据进行对 ...
- rapidjson 使用
生成数组集合的字符串 #include <stdio.h> #include <string> #include <iostream> #include " ...
- 【Vuejs】301- Vue 3.0前的 TypeScript 最佳入门实践
前言 我个人对更严格类型限制没有积极的看法,毕竟各类转类型的骚写法写习惯了. 然鹅最近的一个项目中,是 TypeScript+ Vue,毛计喇,学之...-真香! 1. 使用官方脚手架构建 npm i ...
- Shell排序 C&&C++
Shell排序 Shell排序是大量数据需要排序时,更为高效的插入排序.它的算法思想基于插入排序的算法思想 流程: (1)将n个元素数组分成n/2个数字序列,第一个数据和第n/2个数据为一对,等等 ...
- .Net Core 3.0 以及其前版本编写自定义主机,以允许本机程式(转载)
像所有的托管代码一样,.NET Core 应用程序也由主机执行. 主机负责启动运行时(包括 JIT 和垃圾回收器等组件)和调用托管的入口点. 托管 .NET Core 运行时是高级方案,在大多数情况下 ...