你真的懂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.4

3. 问题(修改调用方式)

其他全部代码不改变,仅修改调用方式。具体代码如下

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是我们工程中常用的一个方法。但对于其用法,如果深究,还是有非常多意外的惊喜。如果小伙伴有其他问题,随时欢迎交流讨论

posted @ 2023-04-14 15:35  程序员博博  阅读(240)  评论(0编辑  收藏  举报