關於Java多線程的線程同步和線程通訊的一些小問題(順便分享幾篇高質量的博文)

Java多線程的線程同步和線程通訊的一些小問題(順便分享幾篇質量高的博文)


  • 前言:在學習多線程時,遇到了一些問題,這裏我將這些問題都分享出來,同時也分享了幾篇其餘博客主的博客,而且將我我的的理解也分享給你們,本人的水平有限,若是個人分析或者結論有錯誤但願你們必定要幫我指出來,我好能改正和提升

1、對於線程同步和同步鎖的理解(注:分享了三篇高質量的博客)


如下我精心的挑選了幾篇博文,分別是關於對線程同步的理解和如何選擇線程鎖以及瞭解線程鎖的做用範圍。

<一>線程同步鎖的選擇

1. 這裏我推薦下陸先生的Java代碼質量改進之:同步對象的選擇這篇博文。html

2. 以上推薦的博文是以賣火車票爲例,引出了非同步會致使的錯誤以及同步鎖(監視器)應該若是選擇,應該可以幫助你們理解同步鎖。java


<二>線程同伴鎖用法及同步鎖的做用範圍

1. 這裏我推薦下Java中synchronized同步鎖用法及做用範圍這篇博文。編程

2. 以上的博文將靜態鎖(字節碼文件鎖)和非靜態鎖(this)進行了對比,以及將線程非同步和線程同步下進行了對比,對你們瞭解線程鎖的用法和做用範圍有很大的幫助。多線程

 

<三>對線程同步的理解

1. 這裏我推薦下java中線程同步的理解(很是通俗易懂)這篇博文。ide

2. 以上推薦的博文以很是通俗易懂的觀點解釋了到時什麼同步,將同步理解成了線程同步就是線程排隊,並且舉了一些平常生活中的例子來讓你們理解到底什麼是同伴。學習

 

<四>同步的做用場景

1. 並非說同步在什麼狀況下都是好的,由於線程的同步會帶來較低效率,由於線程同步就表明着線程要排隊,即線程同步鎖會帶來的同步阻塞狀態。this

2. 由於CPU是隨意切換線程的,當咱們想讓當前線程執行以後CPU不隨意切換到其餘線程,或者咱們想要讓某個線程的代碼可以在徹底執行以前不會被搶奪執行權,不會致使從而沒法連續執行,那麼咱們就須要線程的幫助。spa

 

2、線程同步和線程通訊的幾個小細節


如下是我在學習線程同步時,遇到的小問題和個人小感悟

 <一>線程sleep方法的基本用法和注意細節

    1.sleep方法的基本用法.net

Thread.sleep(long millis),傳入毫秒數(1秒 = 1000毫秒),在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操做受到系統計時器和調度程序精度和準確性的影響。該線程不丟失任何監視器的所屬權。(~注:Java技術文檔的意思就是該線程休眠指定的毫秒數,並且休眠狀態暫時失去CPU執行權,並且線程醒來後,該線程不會釋放鎖。)線程

 1 /**
 2  *  3  * Thread.sleep的計時器用法  4  *  5  */
 6 public class ThreadSleepTest {  7 
 8     public static void main(String[] args) {  9         new Thread() { 10  @Override 11             public void run() { 12                 int timeCount = 10; 13                 while (timeCount >= 0) { 14                     if (timeCount == 0) { 15                         System.out.println("新年快樂!~"); 16                         break; 17  } 18                     System.out.println("還剩" + timeCount-- + "秒"); 19                     try { 20                         Thread.sleep(1000); 21                     } catch (InterruptedException e) { 22  e.printStackTrace(); 23  } 24  } 25  } 26  }.start(); 27  } 28 
29 }

 

    2.sleep方法使用的位置選擇

我在使用sleep方法時發現,當sleep的位置不一致所放的位置不一樣時,線程所運行的結果也是大不相同的,如下的代碼是爲了舉例子,並非說這個同步代碼塊就是應這樣寫(其實這段代碼這麼寫是有很大的問題的,由於同步資源的選擇不許確),至於同步資源的選擇我在第二個大問題會講到。

      • A 如下的代碼是sleep方法出如今了售票的代碼塊以前,這時出現了負票。(可能時間上也會致使差別,可是這裏先不考慮時間時間因素,時間因素等下講。)
 1 package javase.week4;  2 
 3 public class SellTrainTickets {  4 
 5     public static void main(String[] args) {  6         new MyThread("窗口1").start();  7         new MyThread("窗口2").start();  8         new MyThread("窗口3").start();  9         new MyThread("窗口4").start(); 10  } 11 
12 } 13 
14 class MyThread extends Thread { 15     
16     static int tickets = 100; 17 
18     public MyThread(String name) { 19         super(name); 20  } 21 
22  @Override 23     public void run() { 24         while (tickets > 0) {//假設這已經減到了1 1>0 而後窗口1 窗口2 窗口3 窗口4 都進入循環
25             try { 26                 Thread.sleep(20); 27                 
28             } catch (InterruptedException e) { 29  e.printStackTrace(); 30  } 31             synchronized (MyThread.class) { 32                 System.out.println(getName() + "賣出了第" + tickets-- + "張票!");//而後0 -1 -2 -3,這時就出現了負票
33  } 34  } 35  } 36 }

 

      • B 如下的代碼是sleep方法出如今了售票的代碼塊以後,這裏沒有出現負票了,在票數100的狀況下,並且時間是20毫秒的狀況下,該段代碼正好保證了時間點上的合理性,可是相同狀況下sleep方法出如今輸出售票以前的代碼就會出現錯誤,即便改變時間和票數其sleep方法出現的位置錯誤,仍是會致使了在票數爲負的狀況。(其實若是票數更改或者時間的改變也可能致使sleep方法出如今售票代碼塊以後的狀況下負票的出現)
 1 public class SellTrainTickets {  2 
 3     public static void main(String[] args) {  4         new MyThread("窗口1").start();  5         new MyThread("窗口2").start();  6         new MyThread("窗口3").start();  7         new MyThread("窗口4").start();  8  }  9 
10 } 11 
12 class MyThread extends Thread { 13     
14     static int tickets = 100; 15 
16     public MyThread(String name) { 17         super(name); 18  } 19 
20  @Override 21     public void run() { 22         while (tickets > 0) {//假設這已經減到了0 5>0 而後窗口1 窗口2 窗口3 窗口4 都進入循環
23             synchronized (MyThread.class) { 24                 System.out.println(getName() + "賣出了第" + tickets-- + "張票!");//而後 3 2 1 0
25  } 26             try { 27                 Thread.sleep(20);//此時票數等於0,這時窗口1 窗口2 窗口3 窗口4 都處於休眠,而後若是這裏的時間合理的話,再次判斷的話,正好在等於0的時候,都沒有線程再次進入循環,也就不會出現負票了
28                 
29             } catch (InterruptedException e) { 30  e.printStackTrace(); 31  } 32  } 33  } 34 }

 

      • C 總結下其實sleep方法出現的位置可能會影響到線程的結果,可是其實通常狀況下是不會這麼樣去使用的,這裏只是爲了演示下sleep方法在位置不一樣的狀況下出現的不一樣的結果,目的是爲了讓你們注意編程的細節。

 

     3.sleep方法的傳入參數的選擇

sleep方法的傳入的毫秒數對於線程的運行結果是有較大的影響的,最直接簡單的影響就是讓運行延遲了,可是除了這個之外其實也讓線程的運行結果發生了變化,順便分享一篇一篇高質量的博文Sleep(0)的妙用

      • A  當傳入的參數爲100時,如下是代碼演示和執行結果,幾乎每個窗口(線程)均可以搶奪到運行權,並且比較分散。
 1 public class TicketsThreadTest {  2 
 3     public static void main(String[] args) {  4         new TicketThread("窗口1").start();  5         new TicketThread("窗口2").start();  6         new TicketThread("窗口3").start();  7         new TicketThread("窗口4").start();  8  }  9 
10 } 11 
12 class TicketThread extends Thread { 13 
14     public TicketThread(String name) { 15         super(name); 16  } 17 
18     private static int ticket = 100; 19 
20     public void run() { 21         while (true) { 22             synchronized (TicketThread.class) { 23                 if (ticket <= 0) { 24                     break; 25  } 26                 System.out.println(getName() + "賣出了第" + ticket-- + "張票!"); 27                 try { 28                     Thread.sleep(100); 29                 } catch (InterruptedException e) { 30  e.printStackTrace(); 31  } 32  } 33  } 34  } 35 }

 

      • B 當傳入的參數爲1時,如下是代碼演示和執行結果,此次執行的效果就不是很好,並非每個線程都能很好的執行到,或者執行得不是很分散。
 1 public class TicketsThreadTest {  2 
 3     public static void main(String[] args) {  4         new TicketThread("窗口1").start();  5         new TicketThread("窗口2").start();  6         new TicketThread("窗口3").start();  7         new TicketThread("窗口4").start();  8  }  9 
10 } 11 
12 class TicketThread extends Thread { 13 
14     public TicketThread(String name) { 15         super(name); 16  } 17 
18     private static int ticket = 100; 19 
20     public void run() { 21         while (true) { 22             synchronized (TicketThread.class) { 23                 if (ticket <= 0) { 24                     break; 25  } 26                 System.out.println(getName() + "賣出了第" + ticket-- + "張票!"); 27                 try { 28                     Thread.sleep(1); 29                 } catch (InterruptedException e) { 30  e.printStackTrace(); 31  } 32  } 33  } 34  } 35 }

 

 

      • C 總結如下時間參數對線程的結果的影響,以賣火車票爲例,當咱們在sleep方法中輸入不一樣的參數,那麼線程的運行結果就發生了變化,由於當咱們給定的休眠期長了,那麼線程的搶奪CPU執行權的速度就放緩了,此時運行的結果就變得比較分散,若是幾乎沒有休眠期那麼搶到執行權的窗口(線程)可能仍是處於領先優點,sleep方法其實讓處於優先地位的暫時休眠讓出了CPU執行權,而後sleep醒來又處於就緒狀態來搶奪資源。這樣不會讓其餘線程變成沒法執行的尷尬境遇。固然後續可使用wait和notify以及notifyAll的方法,讓線程進行有規律地交替運行。

 

<二>明確須要同步的共享資源

       若是這裏同步的是代碼塊不是代碼方法,那麼這裏須要對要同步的共享資源的選擇要準確,若是選擇得不許確會致使結果不理想。

      • A 如下代碼表示選擇的共享代碼塊爲售票的單個輸出語句,此時能夠看出結果,結果出現了負票
 1 public class TicketsThreadTest {  2 
 3     public static void main(String[] args) {  4         new TicketThread("窗口1").start();  5         new TicketThread("窗口2").start();  6         new TicketThread("窗口3").start();  7         new TicketThread("窗口4").start();  8  }  9 
10 } 11 
12 class TicketThread extends Thread { 13 
14     public TicketThread(String name) { 15         super(name); 16  } 17 
18     private static int ticket = 100; 19 
20     public void run() { 21         while (true) { 22                 if (ticket <= 0) { 23                     break; 24  } 25                 synchronized (TicketThread.class) { 26                 System.out.println(getName() + "賣出了第" + ticket-- + "張票!"); 27  } 28                 try { 29                     Thread.sleep(0); 30                 } catch (InterruptedException e) { 31  e.printStackTrace(); 32  } 33  } 34  } 35 }

 

      • B 如下代碼表示選擇的共享代碼塊是while循環的整個代碼塊,此時能夠看出結果,結果沒有出現負票,並且通過了屢次嘗試也沒有出現
 1 public class TicketsThreadTest {  2 
 3     public static void main(String[] args) {  4         new TicketThread("窗口1").start();  5         new TicketThread("窗口2").start();  6         new TicketThread("窗口3").start();  7         new TicketThread("窗口4").start();  8  }  9 
10 } 11 
12 class TicketThread extends Thread { 13 
14     public TicketThread(String name) { 15         super(name); 16  } 17 
18     private static int ticket = 100; 19 
20     public void run() { 21         while (true) { 22             synchronized (TicketThread.class) { 23                 if (ticket <= 0) { 24                     break; 25  } 26                 System.out.println(getName() + "賣出了第" + ticket-- + "張票!"); 27                 try { 28                     Thread.sleep(0); 29                 } catch (InterruptedException e) { 30  e.printStackTrace(); 31  } 32  } 33  } 34  } 35 }

 

      • C 總結在選擇須要同步的代碼塊是必定要注意哪些代碼塊(資源是須要共享的),這裏就要判斷下這些代碼是不是須要共享,將須要共享的資源用synchronized代碼塊包起來

 

<三>線程通訊之while和if的選擇

      • A 如下的代碼使用的是if選擇結構進行線程通訊之間的判斷,能夠發現三個線程之間沒有有規律地交替進行。
 1 package javase.week4;  2 
 3 /**
 4  *  5  * 三個線程之間的通訊使用if選擇語句  6  *  7  */
 8 public class ComunicatedThreadTest {  9     public static void main(String[] args) {  10         Printer1121 p = new Printer1121();  11         new Thread() {  12  @Override  13             public void run() {  14                 while (true) {  15                     try {  16  p.print1();  17                     } catch (Exception e) {  18  e.printStackTrace();  19  }  20  }  21  }  22  }.start();  23         new Thread() {  24  @Override  25             public void run() {  26                 while (true) {  27                     try {  28  p.print2();  29                     } catch (Exception e) {  30  e.printStackTrace();  31  }  32  }  33  }  34  }.start();  35 
 36         new Thread() {  37  @Override  38             public void run() {  39                 while (true) {  40                     try {  41  p.print3();  42                     } catch (Exception e) {  43  e.printStackTrace();  44  }  45  }  46  }  47  }.start();  48  }  49 
 50 }  51 
 52 class Printer1121 {  53     private int flag = 1;  54 
 55     public void print1() throws Exception {  56         synchronized (this) {  57             if (flag != 1) {  58                 this.wait();  59  }  60             Thread.sleep(100);  61             System.out.print(1);  62             System.out.print(2);  63             System.out.print(3);  64             System.out.print(4);  65             System.out.print(5);  66  System.out.println();  67             flag = 2;  68             this.notifyAll();  69  }  70  }  71 
 72     public void print2() throws Exception {  73         synchronized (this) {  74             if (flag != 2) {  75                 this.wait();  76  }  77             Thread.sleep(100);  78             System.out.print("a");  79             System.out.print("b");  80             System.out.print("c");  81             System.out.print("d");  82             System.out.print("e");  83  System.out.println();  84             flag = 3;  85             this.notifyAll();  86  }  87  }  88 
 89     public void print3() throws Exception {  90         synchronized (this) {  91             if (flag != 3) {  92                 this.wait();  93  }  94             Thread.sleep(100);  95             System.out.print("A");  96             System.out.print("B");  97             System.out.print("C");  98             System.out.print("D");  99             System.out.print("E"); 100  System.out.println(); 101             flag = 1; 102             this.notifyAll(); 103  } 104  } 105 }

 

      • B 如下是用while對通訊條件進行循環判斷的,能夠發現三個線程是有規律地循環進行運行的。
 1 package javase.week4;  2 /**
 3  *  4  * 三個線程之間的通訊使用while循環判斷語句  5  *  6  */
 7 public class ComunicatedThreadTest {  8     public static void main(String[] args) {  9         Printer1121 p = new Printer1121();  10         new Thread() {  11  @Override  12             public void run() {  13                 while (true) {  14                     try {  15  p.print1();  16                     } catch (Exception e) {  17  e.printStackTrace();  18  }  19  }  20  }  21  }.start();  22         new Thread() {  23  @Override  24             public void run() {  25                 while (true) {  26                     try {  27  p.print2();  28                     } catch (Exception e) {  29  e.printStackTrace();  30  }  31  }  32  }  33  }.start();  34 
 35         new Thread() {  36  @Override  37             public void run() {  38                 while (true) {  39                     try {  40  p.print3();  41                     } catch (Exception e) {  42  e.printStackTrace();  43  }  44  }  45  }  46  }.start();  47  }  48 
 49 }  50 
 51 class Printer1121 {  52     private int flag = 1;  53 
 54     public void print1() throws Exception {  55         synchronized (this) {  56             while (flag != 1) {  57                 this.wait();  58  }  59             Thread.sleep(100);  60             System.out.print(1);  61             System.out.print(2);  62             System.out.print(3);  63             System.out.print(4);  64             System.out.print(5);  65  System.out.println();  66             flag = 2;  67             this.notifyAll();  68  }  69  }  70 
 71     public void print2() throws Exception {  72         synchronized (this) {  73             while (flag != 2) {  74                 this.wait();  75  }  76             Thread.sleep(100);  77             System.out.print("a");  78             System.out.print("b");  79             System.out.print("c");  80             System.out.print("d");  81             System.out.print("e");  82  System.out.println();  83             flag = 3;  84             this.notifyAll();  85  }  86  }  87 
 88     public void print3() throws Exception {  89         synchronized (this) {  90             while (flag != 3) {  91                 this.wait();  92  }  93             Thread.sleep(100);  94             System.out.print("A");  95             System.out.print("B");  96             System.out.print("C");  97             System.out.print("D");  98             System.out.print("E");  99  System.out.println(); 100             flag = 1; 101             this.notifyAll(); 102  } 103  } 104 }

      • C 這裏進行下緣由分析,爲何會出現這樣的狀況?首先wait方法在同步代碼塊裏被調用了,那麼此時調用者直接在wait處等待了,而後等待下次被notify或者notifyAll喚醒。而if選擇結構在判斷一次以後就順序執行了,當線程被喚醒時,咱們但願的是再次判斷一次條件看可以繼續進行,可是if沒法作到,由於上次已經判斷正確了,它只會向下繼續執行此時就會又出現隨意無規律交替運行,可是while是循環判斷,即便判斷過一次了,可是每次執行完它會再次判斷,這時就會讓三個線程的運行結果有規律了。
相關文章
相關標籤/搜索