我們都知道,不論是在學習、面試和作業中,多執行緒一直都被視為很重要的一項技術,也是開發人員交流中老生常談的話題,
相信正在閱讀的你,腦海中已經浮現出了多執行緒的知識,許多小伙伴對于多執行緒都有這樣的困擾:知識一看就會,遇到場景就廢,螢屏前的你是否也有這樣的困擾呢?
就比如我們都知道synchronized實作同步具體表現為以下三種形式:
- 對于普通同步方法,鎖是當前實體物件
- 對于靜態同步方法,鎖是當前類的Class物件
- 對于同步方法塊,鎖是synchronized括號里配置的物件
但是一到真實場景中,就難以判斷synchronized鎖的是誰了,
那么本篇文章,參考了著名的”8鎖現象“,每個問題都會附一套代碼,來幫助你徹底理解synchronized到底鎖了什么,讓你不論在多么復雜的場景下,都可以正確地判斷鎖的是誰,
想深入了解synchronized鎖的讀者
可以閱讀筆者的上一篇作品:synchronized的實作原理與應用&Java物件的記憶體布局
八鎖現象
8鎖,就是關于鎖的八個問題!
讀者可以檢驗一下自己是否能將這八個問題都答對呢?
問題1
運行下面的代碼,兩個執行緒執行,是先列印發訊息還是打電話呢?
public class Demo1 {
public static void main(String[] args) {
WeChat weChat = new WeChat();
new Thread(()->{
weChat.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
weChat.call();
},"B").start();
}
}
class WeChat{
public synchronized void sendMessage(){
System.out.println("send message");
}
public synchronized void call(){
System.out.println("call");
}
}
答: 先發訊息,1秒后打電話
決議:
在這種情況下,synchronized鎖的是當前的實體物件,兩個方法用的是同一個鎖(都是同一個weChat物件),因為執行緒A先拿到了鎖,所以執行緒A先執行,即先發訊息
問題2
在sendMessage方法中添加5秒的延時,運行結果又是怎樣的呢?
代碼(僅修改了WeChat類的sendMessage方法):
public class Demo2 {
public static void main(String[] args) {
WeChat weChat = new WeChat();
new Thread(()->{
weChat.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
weChat.call();
},"B").start();
}
}
class WeChat{
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public synchronized void call(){
System.out.println("call");
}
}
答: 程式運行5秒后發訊息,1秒后打電話
決議:
和上個問題一樣,只不過是在sendMessage方法中打了個馬虎眼,延遲了5秒,但是這并沒有影響執行緒A率先獲得物件的鎖,所以在這種情況下,synchronized鎖的是當前的實體物件,兩個方法用的是同一個鎖(都是同一個weChat物件),因為執行緒A先拿到了鎖,所以執行緒A先執行,即先發訊息
問題3
新增一個普通非同步方法hello(),sendMessage()中延遲5秒,讓B執行緒呼叫hello(),請問執行結果?
代碼(在WeChat方法中新增了hello()方法,并且由執行緒B呼叫的call()改為hello()):
public class Demo3 {
public static void main(String[] args) {
WeChat weChat = new WeChat();
new Thread(()->{
weChat.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
weChat.hello();
},"B").start();
}
}
class WeChat{
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public synchronized void call(){
System.out.println("call");
}
public void hello(){
System.out.println("hello");
}
}
答: 先hello,然后再發訊息
決議:
從代碼中可以看到,hello是非鎖方法,所以執行緒A和執行緒B不存在鎖的競爭,只有執行緒A拿到了物件的鎖,又因為
sendMessage()有5秒的延遲,所以hello先執行,
問題4
兩個WeChat物件,分別在A、B中呼叫sendMessage()和call(),sendMessage()中延遲5秒,請問執行結果?
代碼(在主執行緒中創建兩個WeChat物件,分別在A、B中呼叫sendMessage()和call()):
public class Demo4 {
public static void main(String[] args) {
WeChat weChat1 = new WeChat();
WeChat weChat2 = new WeChat();
new Thread(()->{
weChat1.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
weChat2.call();
},"B").start();
}
}
class WeChat{
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public synchronized void call(){
System.out.println("call");
}
}
答: 程式運行1秒后打電話,程式運行5秒后發短信
決議:
因為主程式中創建了兩個WeChat物件,執行緒A獲得了weChat1的鎖,執行緒B獲得了weChat2的鎖,這兩個鎖是相互獨立的,互不影響,所以在主程式中延遲1秒后,執行緒B執行,程式剛開始執行緒A就開始執行了,在
sendMessage()方法中延遲了5秒后才列印”send message“,
問題5
將call()和sendMessage()都設定成static,sendMessage()中有延遲5秒,讓A和B分別呼叫sendMessage()和call(),請問執行結果?
代碼(將call()和sendMessage()方法設定成靜態方法):
public class Demo5 {
public static void main(String[] args) {
WeChat weChat = new WeChat();
new Thread(()->{
weChat.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
weChat.call();
},"B").start();
}
}
class WeChat{
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public static synchronized void call(){
System.out.println("call");
}
}
答: 先發訊息,后打電話
決議:
對于靜態的同步方法來說,鎖是當前類的Class物件的,有且只有一把,所以是執行緒A先拿到這把鎖,待執行緒A執行完后釋放鎖,執行緒B才能拿到,
問題6
在問題5的基礎上,分別用兩個WeChat實體物件分別在執行緒A和執行緒B中呼叫sendMessage()和call(),請問執行結果?
代碼(在主執行緒中創建兩個WeChat物件,分別在A、B中呼叫sendMessage()和call()):
public class Demo6 {
public static void main(String[] args) {
WeChat weChat1 = new WeChat();
WeChat weChat2 = new WeChat();
new Thread(()->{
weChat1.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
weChat2.call();
},"B").start();
}
}
class WeChat{
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public static synchronized void call(){
System.out.println("call");
}
}
答: 先發短信,后打電話
決議:
仍是同樣的原理,對于靜態同步方法來說,鎖是當前類的Class物件的,不論有多少個實體物件,有且只有一把,所以在這個例子中,仍是執行緒A先獲得鎖,待執行緒A執行完后釋放鎖,執行緒B才能拿到,
問題7
一個延遲4秒的靜態同步方法sendMessage()被A中的weChat物件呼叫,一個普通同步方法call()被B中的同一個weChat物件呼叫,請問執行結果?
代碼(sendMessage()方法是靜態同步方法,call()方法是普通同步方法):
public class Demo7 {
public static void main(String[] args) {
WeChat weChat = new WeChat();
new Thread(()->{
weChat.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
weChat.call();
},"B").start();
}
}
class WeChat{
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public synchronized void call(){
System.out.println("call");
}
}
答: 先打電話,后發短信
決議:
執行緒A和執行緒B獲得的鎖不一樣,執行緒A呼叫靜態同步的
sendMessage()方法,獲得的鎖是當前類的Class物件的;執行緒B呼叫普通同步的call()方法,獲得的鎖是當前實體物件的,
問題8
2個不同的WeChat實體物件,分別在執行緒A和執行緒B中呼叫一個延遲4秒的靜態同步方法sendMessage()和一個普通同步方法call(),請問執行結果?
代碼(在主執行緒中創建兩個WeChat物件,分別在A、B中呼叫sendMessage()和call()):
public class Demo8 {
public static void main(String[] args) {
WeChat weChat1 = new WeChat();
WeChat weChat2 = new WeChat();
new Thread(()->{
weChat1.sendMessage();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
weChat2.call();
},"B").start();
}
}
class WeChat{
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send message");
}
public synchronized void call(){
System.out.println("call");
}
}
答: 先打電話,后發短信
決議:
執行緒A獲得的鎖是有且僅有一個的當前類的Class的鎖,執行緒B獲得的是實體物件weChat2的鎖
總結
讀到這里,恭喜你,已經渡完了八個劫,希望這”八鎖現象“可以幫助你徹底理解synchronized到底鎖了什么,
看完了這些問題,讀者可能會意識到,光知道了理論知識是遠遠不夠的,我們需要在實際場景中有判斷能力和執行能力,那么執行緒的使用場景有哪些呢?
- 執行后臺任務,在很多場景中,可能會有一些定時的批量任務,比如定時發送短信、定時生成批量檔案,在這些場景中可以通過多執行緒的來執行
- 異步處理,比如在用戶注冊成功以后給用戶發送優惠券或者短信,可以通過異步的方式來執行,一方面提升主程式的執行性能;另一方面可以解耦核心功能,防止非核心功能對核心功能造成影響
- 分布式處理,比如fork/join,將一個任務拆分成多個子任務分別執行
- BIO模型中的執行緒任務分發,也是一種比較常見的使用場景,一個請求對應一個執行緒
最后感謝大家的閱讀,不正確的地方,還希望大家來斧正,我們一起探討技術問題,覺得寫得好的,給個贊,點個關注吧,謝謝,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/389401.html
標籤:其他
