Java多線程——線程八鎖案例分析
摘要:本文主要學習了多線程並發中的一些案例。
部分內容來自以下博客:
https://blog.csdn.net/dyt443733328/article/details/80019352
多線程的八個案例
通過分析代碼,推測打印結果,並運行代碼進行驗證。
1)兩個線程調用同一個對象的兩個同步方法
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 Number number = new Number(); 4 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 number.getOne(); 9 } 10 }).start(); 11 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 number.getTwo(); 16 } 17 }).start(); 18 } 19 } 20 21 class Number { 22 public synchronized void getOne() { 23 System.out.println("one"); 24 } 25 26 public synchronized void getTwo() { 27 System.out.println("two"); 28 } 29 }
運行結果如下:
1 one 2 two
結果分析:
被synchronized修飾的方法,鎖的對象是方法的調用者。因為兩個方法的調用者是同一個,所以兩個方法用的是同一個鎖,先調用方法的先執行。
2)新增Thread.sleep()給某個方法
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 Number number = new Number(); 4 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 number.getOne(); 9 } 10 }).start(); 11 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 number.getTwo(); 16 } 17 }).start(); 18 } 19 } 20 21 class Number { 22 public synchronized void getOne() { 23 try { 24 Thread.sleep(1000); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 System.out.println("one"); 29 } 30 31 public synchronized void getTwo() { 32 System.out.println("two"); 33 } 34 }
運行結果如下:
1 // 等待一秒。 2 one 3 two
結果說明:
被synchronized修飾的方法,鎖的對象是方法的調用者。因為兩個方法的調用者是同一個,所以兩個方法用的是同一個鎖,先調用方法的先執行,第二個方法只有在第一個方法執行完釋放鎖之后才能執行。
3)新增一個線程調用新增的一個普通方法
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 Number number = new Number(); 4 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 number.getOne(); 9 } 10 }).start(); 11 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 number.getTwo(); 16 } 17 }).start(); 18 19 new Thread(new Runnable() { 20 @Override 21 public void run() { 22 number.getThree(); 23 } 24 }).start(); 25 } 26 } 27 28 class Number { 29 public synchronized void getOne() { 30 try { 31 Thread.sleep(1000); 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 System.out.println("one"); 36 } 37 38 public synchronized void getTwo() { 39 System.out.println("two"); 40 } 41 42 public void getThree() { 43 System.out.println("three"); 44 } 45 }
運行結果如下:
1 three 2 // 等待一秒。 3 one 4 two
結果說明:
新增的方法沒有被synchronized修飾,不是同步方法,不受鎖的影響,所以不需要等待。其他線程共用了一把鎖,所以還需要等待。
4)兩個線程調用兩個對象的同步方法,其中一個方法有Thread.sleep()
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 Number numberOne = new Number(); 4 Number numberTwo = new Number(); 5 6 new Thread(new Runnable() { 7 @Override 8 public void run() { 9 numberOne.getOne(); 10 } 11 }).start(); 12 13 new Thread(new Runnable() { 14 @Override 15 public void run() { 16 numberTwo.getTwo(); 17 } 18 }).start(); 19 } 20 } 21 22 class Number { 23 public synchronized void getOne() { 24 try { 25 Thread.sleep(1000); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 System.out.println("one"); 30 } 31 32 public synchronized void getTwo() { 33 System.out.println("two"); 34 } 35 }
運行結果如下:
1 two 2 // 等待一秒。 3 one
結果說明:
被synchronized修飾的方法,鎖的對象是方法的調用者。因為用了兩個對象調用各自的方法,所以兩個方法的調用者不是同一個,所以兩個方法用的不是同一個鎖,后調用的方法不需要等待先調用的方法。
5)將有Thread.sleep()的方法設置為static方法,並且讓兩個線程用同一個對象調用兩個方法
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 Number number = new Number(); 4 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 number.getOne(); 9 } 10 }).start(); 11 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 number.getTwo(); 16 } 17 }).start(); 18 } 19 } 20 21 class Number { 22 public static synchronized void getOne() { 23 try { 24 Thread.sleep(1000); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 System.out.println("one"); 29 } 30 31 public synchronized void getTwo() { 32 System.out.println("two"); 33 } 34 }
運行結果如下:
1 two 2 // 等待一秒。 3 one
結果說明:
被synchronized和static修飾的方法,鎖的對象是類的class對象。僅僅被synchronized修飾的方法,鎖的對象是方法的調用者。因為兩個方法鎖的對象不是同一個,所以兩個方法用的不是同一個鎖,后調用的方法不需要等待先調用的方法。
6)將兩個方法均設置為static方法,並且讓兩個線程用同一個對象調用兩個方法
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 Number number = new Number(); 4 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 number.getOne(); 9 } 10 }).start(); 11 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 number.getTwo(); 16 } 17 }).start(); 18 } 19 } 20 21 class Number { 22 public static synchronized void getOne() { 23 try { 24 Thread.sleep(1000); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 System.out.println("one"); 29 } 30 31 public static synchronized void getTwo() { 32 System.out.println("two"); 33 } 34 }
運行結果如下:
1 // 等待一秒。 2 one 3 two
結果說明:
被synchronized和static修飾的方法,鎖的對象是類的class對象。因為兩個同步方法都被static修飾了,所以兩個方法用的是同一個鎖,后調用的方法需要等待先調用的方法。
7)將兩個方法中有Thread.sleep()的方法設置為static方法,另一個方法去掉static修飾,讓兩個線程用兩個對象調用兩個方法
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 Number numberOne = new Number(); 4 Number numberTwo = new Number(); 5 6 new Thread(new Runnable() { 7 @Override 8 public void run() { 9 numberOne.getOne(); 10 } 11 }).start(); 12 13 new Thread(new Runnable() { 14 @Override 15 public void run() { 16 numberTwo.getTwo(); 17 } 18 }).start(); 19 } 20 } 21 22 class Number { 23 public static synchronized void getOne() { 24 try { 25 Thread.sleep(1000); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 System.out.println("one"); 30 } 31 32 public synchronized void getTwo() { 33 System.out.println("two"); 34 } 35 }
運行結果如下:
1 two 2 // 等待一秒。 3 one
結果說明:
被synchronized和static修飾的方法,鎖的對象是類的class對象。僅僅被synchronized修飾的方法,鎖的對象是方法的調用者。即便是用同一個對象調用兩個方法,鎖的對象也不是同一個,所以兩個方法用的不是同一個鎖,后調用的方法不需要等待先調用的方法。
8)將兩個方法均設置為static方法,並且讓兩個線程用同一個對象調用兩個方法
代碼如下:
1 public class Demo { 2 public static void main(String[] args) { 3 Number numberOne = new Number(); 4 Number numberTwo = new Number(); 5 6 new Thread(new Runnable() { 7 @Override 8 public void run() { 9 numberOne.getOne(); 10 } 11 }).start(); 12 13 new Thread(new Runnable() { 14 @Override 15 public void run() { 16 numberTwo.getTwo(); 17 } 18 }).start(); 19 } 20 } 21 22 class Number { 23 public static synchronized void getOne() { 24 try { 25 Thread.sleep(1000); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 System.out.println("one"); 30 } 31 32 public static synchronized void getTwo() { 33 System.out.println("two"); 34 } 35 }
運行結果如下:
1 // 等待一秒。 2 one 3 two
結果說明:
被synchronized和static修飾的方法,鎖的對象是類的class對象。因為兩個同步方法都被static修飾了,即便用了兩個不同的對象調用方法,兩個方法用的還是同一個鎖,后調用的方法需要等待先調用的方法。
總結
一個類里面如果有多個synchronized方法,在使用同一個對象調用的前提下,某一個時刻內,只要一個線程去調用其中的一個synchronized方法了,其他的線程都只能等待,換句話說,某一時刻內,只能有唯一一個線程去訪問這些synchronized方法。
鎖的是當前對象this,被鎖定后,其他線程都不能進入到當前對象的其他的synchronized方法。
加個普通方法后發現和同步鎖無關。
換成靜態同步方法后,情況又變化。
所有的非靜態同步方法用的都是同一把鎖:實例對象本身。
也就是說如果一個對象的非靜態同步方法獲取鎖后,該對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖,可是其他對象的非靜態同步方法因為跟該對象的非靜態同步方法用的是不同的鎖,所以毋須等待該對象的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。
所有的靜態同步方法用的也是同一把鎖:類對象本身。
這兩把鎖是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間不會有競爭條件。但是一旦一個靜態同步方法獲取鎖后,其他的靜態同步方法都必須等待該方法釋放鎖后才能獲取鎖,而不管是同一個對象的靜態同步方法,還是其他對象的靜態同步方法,只要它們屬於同一個類的對象,那么就需要等待當前正在執行的靜態同步方法釋放鎖。