摘要:異步線程運行線程運行我們創建兩個線程來調用同一業務對象的相同功能時可以看到下面輸出線程運行線程運行兩個線程在一起執行方法并且交叉打印也就是說當我們啟動一個線程執行某個方法的時候就是異步執行至于為啥要這樣演示是因為下面的同步同步將方法上加入關
異步
public class PrintObject { public void printString(){ System.out.println("begin"); if(Thread.currentThread().getName().equals("a")){ PrintStream printStream = System.out; printStream.println("線程 a 運行"); } if(Thread.currentThread().getName().equals("b")){ System.out.println("線程 b 運行"); } System.out.println("end"); } }
public static void main(String[] a) { PrintObject pb = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); thread1.start(); Thread thread2 = new Thread(pb::printString); thread2.setName("b"); thread2.start(); }
我們創建兩個線程來調用同一業務對象的相同功能時, 可以看到下面輸出.
begin begin 線程 a 運行 end 線程 b 運行 end
兩個線程在一起執行 printString 方法, 并且交叉打印. 也就是說當我們啟動一個線程執行某個方法的時候就是異步執行, 至于為啥要這樣演示, 是因為下面的同步.
同步將 synchronized public void printString() 方法上加入 synchronized 關鍵字, 來使方法同步.
執行結果:
begin 線程 a 運行 end begin 線程 b 運行 end
那么為什么加入 synchronized 關鍵字后就會同步呢? 這是因為關鍵字 synchronized 會取得一把對象鎖, 而不是把一段代碼或方法當做鎖; 哪個線程先執行帶 synchronized 關鍵字的方法, 哪個線程就持有該方法所屬的對象的鎖 Look, 那么其他線程只能呈等待狀態.
這里有個前提是多個線程訪問同一個對象, 下面演示的是多個線程訪問不同的對象.
public class PrintObject { synchronized public void printString(){ System.out.println("begin"); if(Thread.currentThread().getName().equals("a")){ PrintStream printStream = System.out; printStream.println("線程 a 運行"); try { Thread.sleep(100000); } catch (InterruptedException e) { } } if(Thread.currentThread().getName().equals("b")){ System.out.println("線程 b 運行"); } System.out.println("end"); } }
public static void main(String[] a) { PrintObject pb = new PrintObject(); PrintObject pb1 = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); thread1.start(); Thread thread2 = new Thread(pb1::printString); thread2.setName("b"); thread2.start(); }
執行結果
begin 線程 a 運行 begin 線程 b 運行 end
讓 a 線程睡眠 100000 毫秒, 可以看到 a 線程并沒有執行完, b 線程就運行了. 這也能夠證明 synchronized 關鍵字取得是對象鎖.
另外還需要注意一點, 我們使用兩個線程執行同一對象的不同同步方法時, 如果線程 a 在睡眠, 那么線程 b 也會一直等待, 線程 a 執行完畢后再去執行.
注: 同步方法一定是線程安全的.synchronized 鎖重入
如果一個獲取鎖的線程調用其它的synchronized修飾的方法, 會發生什么?
在一個線程使用synchronized方法時調用該對象另一個synchronized方法, 即一個線程得到一個對象鎖后再次請求該對象鎖, 是永遠可以拿到鎖的.
在Java內部, 同一個線程調用自己類中其他synchronized方法/塊時不會阻礙該線程的執行, 同一個線程對同一個對象鎖是可重入的, 同一個線程可以獲取同一把鎖多次, 也就是可以多次重入. 原因是Java中線程獲得對象鎖的操作是以線程為單位的, 而不是以調用為單位的.
這種情況也可以發生在繼承中, 也就是說子類的同步方法調用父類的同步方式時, 時可以鎖重入的.synchronized 同步代碼塊
但是, 如果子類重寫了父類的方法, 并沒有使用 synchronized 關鍵字, 則同步就失效了. 因為子類重寫父類的方法, 當我們調用方法執行代碼時, 執行的是子類的方法, 所以變成了異步執行.
public class PrintObject { public synchronized void printString(){ try { System.out.println(Thread.currentThread().getName() + " 執行"); System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫"); // 讓線程休眠, 模擬出網絡延時 Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + " 共享數據減1"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫"); Thread.sleep(5000); if (Thread.currentThread().getName().equals("b")) { SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); } } catch (InterruptedException e) { e.printStackTrace(); } } }
PrintObject pb = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); Thread thread2 = new Thread(pb::printString); thread2.setName("b"); SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); thread1.start(); thread2.start();
執行結果
47:34 a 執行 a 插入數據到數據庫 a 共享數據減1 a 插入數據到數據庫 b 執行 b 插入數據到數據庫 b 共享數據減1 b 插入數據到數據庫 48:04
我們上面這段程序兩個線程全部執行完所用的時間為 30 秒, 這里可以看出同步方法存在一個很大的弊端.
就是說我們的某個線程開始執行方法時, 無論我們操作的是不是共享數據, 別的線程都會等待此線程釋放鎖. 然后繼續執行.
可是我們在插入數據到數據庫的時候, 并不是在操作共享數據, 那么我們有沒有什么辦法, 只同步操作共享數據的那部分代碼呢?
我們就可以使用 synchronized 同步代碼塊, 將程序修改成下面樣子.
public class PrintObject { public void printString(){ try { System.out.println(Thread.currentThread().getName() + " 執行"); System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫"); // 讓線程休眠, 模擬出網絡延時 Thread.sleep(5000); synchronized(this) { System.out.println(Thread.currentThread().getName() + " 共享數據減1"); Thread.sleep(5000); } System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫"); Thread.sleep(5000); if (Thread.currentThread().getName().equals("b")) { SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); } } catch (InterruptedException e) { e.printStackTrace(); } } }
執行結果
54:12 b 執行 a 執行 b 插入數據到數據庫 a 插入數據到數據庫 a 共享數據減1 a 插入數據到數據庫 b 共享數據減1 b 插入數據到數據庫 54:32
減少了10秒的執行時間, 提高了執行效率.
同步方法和同步代碼塊的鎖都是同一把鎖. 同步方法獲取的是該方法的對象鎖, 而同步代碼塊獲取中的參數是 this, 表示當前對象. 所以獲取的是同一把鎖.靜態同步 synchronized 方法與 synchronized(class) 代碼塊
synchronized 關鍵字可以應用在 static 靜態方法上, 表示當前的 *.java 文件對應的 Class 類進行持鎖.
雖然運行結果與 synchronized 關鍵字加到非 static 靜態方法上的結果類似, 但是是對 Class 類進行加鎖, 而 Class 鎖可以對類的所有對象起作用.
synchronized (DemoApplication.class) { }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/73654.html
摘要:上篇中初探了的一些功能和在前端自動化測試方面的可行性,本篇主要分析下的實現方式和源碼。文件分析完整文件目錄運行生成目錄分析出了及其組件代碼,可用和值的分析的文件和下面的五個文件。相關文章前端自動化上篇初步調研前端自動化下篇實踐應用 上篇中初探了page-monitor的一些功能和在前端自動化測試方面的可行性,本篇主要分析下page-monitor的實現方式和源碼。 mode-modul...
摘要:構造函數默認空閑的最大連接數為個,的時間為秒通過構造函數可以看出默認的空閑的最大連接數為個,的時間為秒。實例化實例化是在實例化時進行的在的構造函數中調用了省略省略緩存操作提供對進行操作的方法分別為和幾個操作。 1.引子 在了解OkHttp的復用連接池之前,我們首先要了解幾個概念。 TCP三次握手 通常我們進行HTTP連接網絡的時候我們會進行TCP的三次握手,然后傳輸數據,然后再釋放連接...
摘要:本篇博客主要針對虛擬機的晚期編譯優化,內存模型與線程,線程安全與鎖優化進行總結,其余部分總結請點擊虛擬總結上篇,虛擬機總結中篇。 本篇博客主要針對Java虛擬機的晚期編譯優化,Java內存模型與線程,線程安全與鎖優化進行總結,其余部分總結請點擊Java虛擬總結上篇 ,Java虛擬機總結中篇。 一.晚期運行期優化 即時編譯器JIT 即時編譯器JIT的作用就是熱點代碼轉換為平臺相關的機器碼...
摘要:安裝后已經完成了安裝,并且等待其他的線程被關閉。激活后在這個狀態會處理事件回調提供了更新緩存策略的機會。并可以處理功能性的事件請求后臺同步推送。廢棄狀態這個狀態表示一個的生命周期結束。 showImg(https://segmentfault.com/img/bVbwWJu?w=2056&h=1536); 不知不覺,已經來到了最后的下篇 其實我寫的東西你如果認真去看,跟著去寫,應該能有...
摘要:前端個靈魂拷問,徹底搞明白你就是中級前端工程師上篇感覺大家比較喜歡看這種類型的文章,以后會多一些。所有依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成之后,這個回調函數才會運行。此規范其實是在推廣過程中產生的。 showImg(https://segmentfault.com/img/bVbwAMU?w=700&h=394); 前端20個靈魂拷問,徹底搞明白你就是中級前端工程師...
閱讀 2768·2021-11-24 10:23
閱讀 1166·2021-11-17 09:33
閱讀 2512·2021-09-28 09:41
閱讀 1428·2021-09-22 15:55
閱讀 3649·2019-08-29 16:32
閱讀 1916·2019-08-29 16:25
閱讀 1065·2019-08-29 11:06
閱讀 3431·2019-08-29 10:55