你真的懂synchronized锁?
1. 前言
synchronized在我们的程序中非常的常见,主要是为了解决多个线程抢占同一个资源。那么我们知道synchronized有多种用法,以下从实践出发,题目由简入深,看你能答对几道题目?
2. 问题
调用代码如下
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024));
SyncTest st = new SyncTest();
// 注意是不同方法
executor.submit(() -> st.sync1_1());
executor.submit(() -> st.sync1_2());
executor.shutdown();
}
问题2.1
锁lock全局对象,会输出什么?
public static final Object LOCK = new Object();
public void sync1_1() {
// 锁住lock对象
synchronized (LOCK) {
sleep("sync1_1");
}
}
public void sync1_2() {
// 锁住lock对象
synchronized (LOCK) {
sleep("sync1_2");
}
}
public static void sleep(String name) {
try {
log.info(name + " get lock");
TimeUnit.SECONDS.sleep(1);
log.info(name + " release lock");
} catch (Exception e) {}
}
点击查看答案(请思考后在点击查看)
[INFO 2023-04-14 15:12:46.639] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync1_1 get lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync1_1 release lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync1_2 get lock]
[INFO 2023-04-14 15:12:48.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync1_2 release lock]
等待线程A执行完成后,线程B才能执行,否则阻塞。符合我们预期。锁的资源就是我们所谓的LOCK对象
问题2.2
锁this对象,会输出什么?this是代表什么?
public void sync2_1() {
synchronized (this) {
sleep("sync2_1");
}
}
public void sync2_2() {
synchronized (this) {
sleep("sync2_2");
}
}
点击查看答案(请思考后在点击查看)
[INFO 2023-04-14 15:12:46.639] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync2_1 get lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync2_1 release lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync2_2 get lock]
[INFO 2023-04-14 15:12:48.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync2_2 release lock]
等待线程A执行完成后,线程B才能执行,否则阻塞。符合我们预期。锁的是调用方,SyncTest st = new SyncTest(); 中的st对象。 st是SyncTest类的一个对象。也就是锁的这个this资源
问题2.3
锁方法,会输出什么? 锁的又是什么资源?
public synchronized void sync3_1() {
sleep("sync3_1");
}
public synchronized void sync3_2() {
sleep("sync3_2");
}
点击查看答案(请思考后在点击查看)
结论同 问题2.2问题2.4
锁static方法,会输出什么? 锁的又是什么资源?
public static synchronized void sync4_1() {
sleep("sync4_1");
}
public static synchronized void sync4_2() {
sleep("sync4_2");
}
点击查看答案(请思考后在点击查看)
[INFO 2023-04-14 15:12:46.639] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync4_1 get lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync4_1 release lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync4_2 get lock]
[INFO 2023-04-14 15:12:48.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync4_2 release lock]
等待线程A执行完成后,线程B才能执行,否则阻塞。符合我们预期。因为是在static上面加锁,而static方法即是类方法,因此他锁的是这个类,也就是对SyncTest这个this class加的锁
问题2.5
锁类的class,会输出什么? 锁的又是什么资源?
public void sync5_1() {
synchronized (SyncTest.class) {
sleep("sync5_1");
}
}
public void sync5_2() {
synchronized (SyncTest.class) {
sleep("sync5_2");
}
}
点击查看答案(请思考后在点击查看)
结论同问题2.43. 问题(修改调用方式)
其他全部代码不改变,仅修改调用方式。具体代码如下
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024));
// 这个是new了一个st1
SyncTest st1 = new SyncTest();
executor.submit(() -> st1.sync1_1());
// 这里new了一个st2
SyncTest st2 = new SyncTest();
executor.submit(() -> st2.sync1_2());
executor.shutdown();
}
其他全部不变,问题从2.1 - 2.5重新全部调用一遍。结果又是否相同。
注意一下:调用方为 new 了两个st1以及st2, 如果你完全理解上述问题,这个问题就非常简单了。我们以问题1以及问题2为例:
问题2.1
[INFO 2023-04-14 15:12:46.639] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync1_1 get lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync1_1 release lock]
[INFO 2023-04-14 15:12:47.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync1_2 get lock]
[INFO 2023-04-14 15:12:48.652] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync1_2 release lock]
这是由于无论new几个st,锁的永远是唯一资源lock,因此结论不变
问题2.2
[INFO 2023-04-14 15:26:31.676] [pool-2-thread-2] [] - [SyncTest.java.sleep:86] [sync2_1 get lock]
[INFO 2023-04-14 15:26:31.676] [pool-2-thread-1] [] - [SyncTest.java.sleep:86] [sync2_1 get lock]
[INFO 2023-04-14 15:26:32.688] [pool-2-thread-1] [] - [SyncTest.java.sleep:88] [sync2_1 release lock]
[INFO 2023-04-14 15:26:32.688] [pool-2-thread-2] [] - [SyncTest.java.sleep:88] [sync2_1 release lock]
这个结果就非常有意思了。注意看,线程B并没有阻塞,而是直接获取到了这个资源。也就是synchronized失效了。我们来分析一下为什么synchronized失效了?
我们知道在问题2中,synchronized锁的是this对象,而这个this对象分别为st1, 以及st2。那么是不是就是两个资源了。如果是两个资源,就不存在互斥的作用了,也就是不会相互争夺资源。
请各位读者自己分析问题2.3 - 2.5的第二种代码调用方式的结果。
4. 结论
synchronized是我们工程中常用的一个方法。但对于其用法,如果深究,还是有非常多意外的惊喜。如果小伙伴有其他问题,随时欢迎交流讨论