文档章节

synchronized到底锁住的是谁?

编程SHA
 编程SHA
发布于 06/19 14:44
字数 2585
阅读 38
收藏 5

题目:利用5个线程并发执行,num数字累计计数到10000,并打印。

1 /**
 2 * Description:
 3 * 利用5个线程并发执行,num数字累加计数到10000,并打印。
 4 * 2019-06-13
 5 * Created with OKevin.
 6 */
 7 public class Count {
 8 private int num = 0;
 9 
10 public static void main(String[] args) throws InterruptedException {
11 Count count = new Count();
12 
13 Thread thread1 = new Thread(count.new MyThread());
14 Thread thread2 = new Thread(count.new MyThread());
15 Thread thread3 = new Thread(count.new MyThread());
16 Thread thread4 = new Thread(count.new MyThread());
17 Thread thread5 = new Thread(count.new MyThread());
18 thread1.start();
19 thread2.start();
20 thread3.start();
21 thread4.start();
22 thread5.start();
23 thread1.join();
24 thread2.join();
25 thread3.join();
26 thread4.join();
27 thread5.join();
28 
29 System.out.println(count.num);
30 
31 }
32 
33 private synchronized void increse() {
34 for (int i = 0; i < 2000; i++) {
35 num++;
36 }
37 }
38 
39 class MyThread implements Runnable {
40 @Override
41 public void run() {
42 increse();
43 }
44 }
45 }

这道校招级的并发编程面试题,题目不难,方法简单。其中涉及一个核心知识点——synchronized(当然这题的解法有很多),这也是本文想要弄清的主题。

synchronized被大大小小的程序员广泛使用,有的程序员偷懒,在要求保证线程安全时,不加思索的就在方法前加入了synchronized关键字(例如我刚才那道招级大题)。偷懒归偷懒,CodeReview总是要进行的,面对同事的“指责”,要求优化这个方法,将synchronized使用同步代码块的方式提高效率。

synchronized到底锁住的是谁?

 

synchronized要按照同步代码块来保证线程安全,这可就加在方法“复杂”多了。有:synchronized(this){}这么写的,也有synchronized(Count.class){}这么写的,还有定义了一个private Object obj = new Object; ….synchronized(obj){}这么写的。此时不禁在心里“W*F”。

synchronized你到底锁住的是谁?

synchronized从语法的维度一共有3个用法:

  1. 静态方法加上关键字
  2. 实例方法(也就是普通方法)加上关键字
  3. 方法中使用同步代码块

前两种方式最为偷懒,第三种方式比前两种性能要好。

synchronized从锁的是谁的维度一共有两种情况:

  1. 锁住类
  2. 锁住对象实例

我们还是从直观的语法结构上来讲述synchronized。

1)静态方法上的锁

静态方法是属于“类”,不属于某个实例,是所有对象实例所共享的方法。也就是说如果在静态方法上加入synchronized,那么它获取的就是这个类的锁,锁住的就是这个类

2)实例方法(普通方法)上的锁

实例方法并不是类所独有的,每个对象实例独立拥有它,它并不被对象实例所共享。这也比较能推出,在实例方法上加入synchronized,那么它获取的就是这个累的锁,锁住的就是这个对象实例

那锁住类还是锁住对象实例,这跟我线程安全关系大吗?大,差之毫厘谬以千里的大。为了更好的理解锁住类还是锁住对象实例,在进入“3)方法中使用同步代码块”前,先直观的感受下这两者的区别。

对实例方法(普通方法)上加关键字锁住对象实例锁的解释

首先定义一个Demo类,其中的实例方法加上了synchronized关键字,按照所述也就是说锁住的对象实例。

1 /**
 2 * Description:
 3 * 死循环,目的是两个线程抢占一个锁时,只要其中一个线程获取,另一个线程就会一直阻塞
 4 * 2019-06-13
 5 * Created with OKevin.
 6 */
 7 public class Demo {
 8 
 9 public synchronized void demo() {
10 while (true) { //synchronized方法内部是一个死循环,一旦一个线程持有过后就不会释放这个锁
11 System.out.println(Thread.currentThread());
12 }
13 }
14 }

可以看到在demo方法中定义了一个死循环,一旦一个线程持有这个锁后其他线程就不可能获取这个锁。结合上述synchronized修饰实例方法锁住的是对象实例,如果两个线程针对的是一个对象实例,那么其中一个线程必然不可能获取这个锁;如果两个线程针对的是两个对象实例,那么这两个线程不相关均能获取这个锁。

自定义线程,调用demo方法。

1 /**
 2 * Description:
 3 * 自定义线程
 4 * 2019-06-13
 5 * Created with OKevin.
 6 */
 7 public class MyThread implements Runnable {
 8 private Demo demo;
 9 
10 public MyThread(Demo demo) {
11 this.demo = demo;
12 }
13 
14 @Override
15 public void run() {
16 demo.demo();
17 }
18 }

测试程序1:两个线程抢占一个对象实例的锁

1 /**
 2 * Description:
 3 * 两个线程抢占一个对象实例的锁
 4 * 2019-06-13
 5 * Created with OKevin.
 6 */
 7 public class Main1 {
 8 public static void main(String[] args) {
 9 Demo demo = new Demo();
10 Thread thread1 = new Thread(new MyThread(demo));
11 Thread thread2 = new Thread(new MyThread(demo));
12 thread1.start();
13 thread2.start();
14 }
15 }

synchronized到底锁住的是谁?

 

如上图所示,输出结果显然只会打印一个线程的信息,另一个线程永远也获取不到这个锁。

测试程序2:两个线程分别抢占两个对象实例的锁

1 /**
 2 * Description:
 3 * 两个线程分别抢占两个对象实例的锁
 4 * 2019-06-13
 5 * Created with OKevin.
 6 */
 7 public class Main2 {
 8 public static void main(String[] args) {
 9 Demo demo1 = new Demo();
10 Demo demo2 = new Demo();
11 Thread thread1 = new Thread(new MyThread(demo1));
12 Thread thread2 = new Thread(new MyThread(demo2));
13 thread1.start();
14 thread2.start();
15 }
16 }

synchronized到底锁住的是谁?

 

如上图所示,显然,两个线程均进入到了demo方法,也就是均获取到了锁,证明,两个线程抢占的就不是同一个锁,这就是synchronized修饰实例方法时,锁住的是对象实例的解释。

对静态方法上加关键字锁住类锁的解释

静态方法是类所有对象实例所共享的,无论定义多少个实例,是要是静态方法上的锁,它至始至终只有1个。将上面的程序Demo中的方法加上static,无论使用“测试程序1”还是“测试程序2”,均只有一个线程可以抢占到锁,另一个线程仍然是永远无法获取到锁。

让我们重新回到从语法结构上解释synchronized。

3)方法中使用同步代码块

程序的改良优化需要建立在有坚实的基础,如果在不了解其内部机制,改良也仅仅是“形式主义”。

结合开始CodeReview的例子:

你的同事在CodeReview时,要求你将实例方法上的synchronized,改为效率更高的同步代码块方式。在你不清楚同步代码的用法时,网上搜到了一段synchronized(this){}代码,复制下来发现也能用,此时你以为你改良优化了代码。但实际上,你可能只是做了一点形式主义上的优化。

为什么这么说?这需要清楚地认识同步代码块到底应该怎么用。

3.1)synchronized(this){...}

this关键字所代表的意思是该对象实例,换句话说,这种用法synchronized锁住的仍然是对象实例,他和public synchronized void demo(){}可以说仅仅是做了语法上的改变。

1 /**
 2 * 2019-06-13
 3 * Created with OKevin.
 4 **/
 5 public class Demo {
 6 
 7 public synchronized void demo1() {
 8 while (true) { //死循环目的是为了让线程一直持有该锁
 9 System.out.println(Thread.currentThread());
10 }
11 }
12 
13 public synchronized void demo2() {
14 while (true) {
15 System.out.println(Thread.currentThread());
16 }
17 }
18 }

改为以下方式:

1 /**
 2 * Description:
 3 * synchronized同步代码块对本实例加锁(this)
 4 * 假设demo1与demo2方法不相关,此时两个线程对同一个对象实例分别调用demo1与demo2,只要其中一个线程获取到了锁即执行了demo1或者demo2,此时另一个线程会永远处于阻塞状态
 5 * 2019-06-13
 6 * Created with OKevin.
 7 */
 8 public class Demo {
 9 
10 public void demo1() {
11 synchronized (this) {
12 while (true) { //死循环目的是为了让线程一直持有该锁
13 System.out.println(Thread.currentThread());
14 }
15 }
16 }
17 
18 public void demo2() {
19 synchronized (this) {
20 while (true) {
21 System.out.println(Thread.currentThread());
22 }
23 }
24 }
25 }

也许后者在JVM中可能会做一些特殊的优化,但从代码分析上来讲,两者并没有做到很大的优化,线程1执行demo1,线程2执行demo2,由于两个方法均是抢占对象实例的锁,只要有一个线程获取到锁,另外一个线程只能阻塞等待,即使两个方法不相关。

3.2)private Object obj = new Object(); synchronized(obj){...}

1 /**
 2 * Description:
 3 * synchronized同步代码块对对象内部的实例加锁
 4 * 假设demo1与demo2方法不相关,此时两个线程对同一个对象实例分别调用demo1与demo2,均能获取各自的锁
 5 * 2019-06-13
 6 * Created with OKevin.
 7 */
 8 public class Demo {
 9 private Object lock1 = new Object();
10 private Object lock2 = new Object();
11 
12 public void demo1() {
13 synchronized (lock1) {
14 while (true) { //死循环目的是为了让线程一直持有该锁
15 System.out.println(Thread.currentThread());
16 }
17 }
18 }
19 
20 public void demo2() {
21 synchronized (lock2) {
22 while (true) {
23 System.out.println(Thread.currentThread());
24 }
25 }
26 }
27 }

经过上面的分析,看到这里,你可能会开始懂了,可以看到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){...},这才是对你代码的改良优化。

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:787707172,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。
 

© 著作权归作者所有

编程SHA
粉丝 21
博文 207
码字总数 503378
作品 0
长沙
私信 提问
线程安全到底是什么意思?

本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/java/thread_safe/ 多线程编程中的三个核心概念 原子性 这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个...

李矮矮
2016/10/17
99
0
synchronized修饰方法时的误解

以前一直没注意synchronized关键字在修饰方法时候的问题,今天偶然看HashTable的时候突然觉得自己有个误区,不知道各位大神是否有在意。 个人一直以为synchronized修饰方法是锁的是方法,一个...

飞一般程序员
2018/03/28
0
0
synchronized 持有的锁分析?是对象的锁还是类的锁?

1.synchronized关键字是用来控制多线程同步时候使用的一种方式,在多线程的状态下,控制synchronized代码段不被多个线程同时执行。可以加在一段代码上,也可以加在方法上。 2.synchronized实...

丑的想整容
2018/01/13
6
0
java 线程同步 字符串 锁

有个 java 同步的问题,我是用 一个字符串 作为 synchronized 锁住的对象。代码 如下 import java.util.ArrayList; import java.util.Date; class Locktest{ private int i = 10; } class L......

xianwu13
2012/12/17
1K
3
并发编程笔记二:synchronized锁住了谁?

在并发编程中要使用到关键字,当我们用关键字修饰一个方法时,代表着一个锁(Lock),那么这个锁的对象是什么,也就是它锁住了谁? 的使用情况大概就是下面几种: synchronized修饰非静态方法...

binxin5108
2016/12/23
369
0

没有更多内容

加载失败,请刷新页面

加载更多

java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
今天
6
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
5
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
15
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
17
0
jquery--DOM操作基础

本文转载于:专业的前端网站➭jquery--DOM操作基础 元素的访问 元素属性操作 获取:attr(name);$("#my").attr("src"); 设置:attr(name,value);$("#myImg").attr("src","images/1.jpg"); ......

前端老手
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部