加不加 synchronized 有什么区别?
synchronized 作为悲观锁,锁住了什么?
之前 2 篇文章我们已经知道 synchronized 的使用方法以及锁的内容(实例对象和Class类对象),这已经涵盖了这个关键字的基本内容了,今天这篇想介绍一下另一种写法,就是 同步代码块 ,它实现了更细粒度的同步方式。下面来见分晓。
先给大家介绍一下同步代码块怎么写,大体的代码框架是这样:
synchronized(xxx) { }
xxx 可以是 this 或者 Object 或者 xxx.class,下面我们就根据这 3 种不同加锁方式进行展开讨论。
表示的是锁住当前对象,和原来使用同步实例方式一样,锁住了当前的对象。
public class SynchronizedCodeTest { public static void main(String[] args) { SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest(); for (int i = 0; i < 5; i ++) { Thread thread = new Thread(() -> { synchronizedCodeTest.testSynchronizedCode(); }); thread.start(); } } int count = 0; public void testSynchronizedCode() { System.out.printf("%s-testSynchronizedCode-start-count=%s/n", Thread.currentThread().getName(), count); synchronized (this) { System.out.printf("%s-testSynchronizedCode-synchronized-start-count=%s/n", Thread.currentThread().getName(), count); count ++; System.out.printf("%s-testSynchronizedCode-synchronized-end-count=%s/n", Thread.currentThread().getName(), count); } System.out.printf("%s-testSynchronizedCode-end-count=%s/n", Thread.currentThread().getName(), count); } }
运行结果:
我们主要关注红色框和蓝色框的这部分结果,红色框里面是同步块的代码,线程之间是互斥的,但是蓝色框中 Thread-0 在执行同步块的过程中,其他线程非同步块也在执行,这里说明了锁的粒度确实变小了,变成了方法里面的同步块代码之间互斥,非同步块代码不互斥,count 的值最终是 5,说明到执行到同步块时,同一时刻只有一个线程在执行。
我们再写个测试代码,看一看 synchronized(this)
是锁住什么?
public class SynchronizedCodeTest { public static void main(String[] args) { for (int i = 0; i < 5; i ++) { SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest(); Thread thread = new Thread(() -> { synchronizedCodeTest.testSynchronizedCode(); }); thread.start(); } } }
运行结果:
观察到红色框里面就可以发现,这时候同步块不起效果了,并且 count 最终都是 1,证明 synchronized(this)
锁住的是当前的对象,和 public synchronized void testSynchronizedMethod()
一样。
同步代码块带来了灵活性,它不再只是锁住当前对象了,可以锁住任何我们创建的对象,下面就来看看。
public class SynchronizedCodeTest { public static void main(String[] args) { Object lock = new Object(); SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest(lock); for (int i = 0; i < 5; i ++) { Thread thread = new Thread(() -> { synchronizedCodeTest.testSynchroniedLock(); }); thread.start(); } } int count = 0; Object lock = null; public SynchronizedCodeTest(Object lock) { this.lock = lock; } public void testSynchroniedLock() { System.out.printf("%s-testSynchroniedLock-start-count=%s/n", Thread.currentThread().getName(), count); synchronized (lock) { System.out.printf("%s-testSynchroniedLock-synchronized-start-count=%s/n", Thread.currentThread().getName(), count); count ++; System.out.printf("%s-testSynchroniedLock-synchronized-end-count=%s/n", Thread.currentThread().getName(), count); } System.out.printf("%s-testSynchroniedLock-end-count=%s/n", Thread.currentThread().getName(), count); } }
运行结果:
这段代码,我们创建了一个 lock 对象,作为参数传入到 synchronizedCodeTest 对象里,我们看到结果里面,5 个线程在同步块代码里是串行执行的,count 最终也得到结果是 5。这段代码没有看出锁对象带来的灵活性,下面再看一个例子,把测试代码稍微改一下,让每个线程都有自己的 synchronizedCodeTest 对象。
public static void main(String[] args) { Object lock = new Object(); for (int i = 0; i < 5; i ++) { SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest(lock); Thread thread = new Thread(() -> { synchronizedCodeTest.testSynchroniedLock(); }); thread.start(); } }
运行结果:
结果我们发现,虽然我们为每个线程创建一个 synchronizedCodeTest 对象,但是不管怎么运行,5 个线程的同步代码块都是串行执行的,原因在于,我们只创建了一个 lock 对象,这 5 个 synchronizedCodeTest 的 lock 对象都是同一个,因此竞争资源是同一个,才出现这种情况。看是不是比同步方法灵活多了,上一篇中,我们要让多个实例同步执行,我们需要使用 静态同步方法 ,现在不用了,使用同步代码块就可以,只需要满足锁住同一个实例对象就行。
另外,这个例子的结果是每个实例的 count 最终都为 1,这是因为每个 synchronizedCodeTest 对象都有自己独立的变量 count,所以线程之间互不影响。
再来看看最后一种代码块锁 Class 类,这和 public static synchronized testSynchronizedStatic()
的作用是一样的,区别就只是代码块的锁范围可变。我们直接看看代码例子。
public class SynchronizedCodeTest { public static void main(String[] args) { for (int i = 0; i < 5; i ++) { SynchronizedCodeTest synchronizedCodeTest = new SynchronizedCodeTest(); Thread thread = new Thread(() -> { synchronizedCodeTest.testSynchronizedCodeClass(); }); thread.start(); } } int count = 0; public void testSynchronizedCodeClass() { System.out.printf("%s-testSynchronizedCodeClass-start-count=%s/n", Thread.currentThread().getName(), count); synchronized (SynchronizedCodeTest.class) { System.out.printf("%s-testSynchronizedCodeClass-synchronized-start-count=%s/n", Thread.currentThread().getName(), count); count ++; System.out.printf("%s-testSynchronizedCodeClass-synchronized-end-count=%s/n", Thread.currentThread().getName(), count); } System.out.printf("%s-testSynchronizedCodeClass-end-count=%s/n", Thread.currentThread().getName(), count); } }
运行结果:
每个线程都有自己的实例,但是锁住 Class 会使每个线程实例对象的同步块都是串行执行,这个结果和上面的多个实例锁住同一个 Object 对象的结果是一样的。我个人更倾向于使用锁同一个 Object 对象,而不是锁 Class 类对象。
这篇介绍了 synchronizd 代码块 的 3 种使用方式,并详细介绍了各自的使用方式和区别。简单的列个表。
类型 | 使用方式 | 锁作用范围 |
---|---|---|
this | synchronized(this){} | 锁住当前的实例对象 |
object | synchronized(lock){} | 锁住其他实例对象,比较灵活 |
xxx.class | synchronized(xxx.class){} | 锁住 Class 对象 |
总共用了 3 篇文章来给大家介绍 synchronized
的具体用法,主要是因为之前有些文章一下子就进入 Java 源代码和 JVM 源码,让不少朋友感觉有点吃力,甚至有朋友一看标题就不进来看看了,所以这次先介绍一些比较简单的用法,让大家先了解了解,后面几篇会比较深入 synchronized
原理性的文章,希望大家也能看看,会有非常大的收获的。
原创不易,大家多帮忙转发,点在看。
synchronized 作为悲观锁,锁住了什么?
加不加 synchronized 有什么区别?
从 JVM 视角看看 Java 守护线程
写了那么多年 Java 代码,终于 debug 到 JVM 了
全网最新最简单的 openjdk13 代码编译
了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑
线程最最基础的知识
老板叫你别阻塞了
吃个快餐都能学到串行、并行、并发
泡一杯茶,学一学同异步
进程知多少?
设计模式看了又忘,忘了又看?
觉得文章有用帮忙转发&点赞,多谢朋友们!