06_Java多線程、線程間通訊

1. 線程的概念編程

     1.1多進程與多線程安全

          進程:一個正在執行的程序.每一個進程執行都有一個執行順序,該順序是一個執行路徑,或叫一個控制單元. 一個進程至少有一個線程.多線程

    線程:就是進程中的一個獨立的控制單元. 線程控制這進程的執行.ide

          多進程的缺點:進程切換開銷大;進程間的通訊很不方便。函數

          多線程: 指的是在單個程序中能夠同時運行多個不一樣的線程,執行不一樣的任務,線程切換的開銷小 。this

     1.2線程的狀態spa

          Java語言使用Thread類及其子類的對象來表示線程,新建的線程在它的一個完整的生命週期一般要經歷5種狀態.線程

wKioL1P8Z7agpBPpAACtapy1mAs907.jpg

  

凍結狀態:在sleep和wait時, 既沒有運行資格,有沒有執行權3d

阻塞狀態: 具有運行資格, 沒有執行權code

     1.3線程調度與優先級(多線程的特性:隨機性)

     Java採用搶佔式調度策略,下面幾種狀況下,當前線程會放棄CPU: 
          (1)當前時間片用完; 
          (2)線程在執行時調用了yield() 或sleep() 方法主動放棄;  
          (3)進行I/O 訪問,等待用戶輸入,致使線程阻塞;或者爲等候一個條件變量,線程調用wait()方法;    
          (4)有高優先級的線程參與調度。 
     線程的優先級用數字來表示,範圍從1~10。主線程的默認優先級爲5
          Thread.MIN_PRIORITY=1
          Thread.NORM_PRIORITY=5 

    Thread.MAX_PRIORITY=10


2. 多線程編程方法

     2.1 Thread類簡介

          Thread類綜合了Java程序中一個線程須要擁有的屬性和方法,其構造方法以下:

               public Thread (ThreadGroup group,Runnable target,String name);      

               public Thread();

               public Thread(Runnable target);

               public Thread(Runnable target,String name);

               public Thread(String name);

               public Thread(ThreadGroup group,Runnable target);

               public Thread(ThreadGroup group,String name);  

          Thread類的主要方法以及功能如表

wKioL1P8ZtWyleGJAANdD7K_9YU775.jpg

     2.2 繼承Thread類實現多線程     

    須要重寫run方法實現線程的任務.須要注意的是,程序中不要直接調用此方法,而是調用線程對象的start()方法啓動線程,讓其進入可調度狀態,線程得到調度自動執行run()方法.    

     2.3 實現Runnable接口編寫多線程

    經過 Thread 類的構造函數public Thread(Runnable target)能夠將一個Runnable 接口對象傳遞給線程,線程在調度時將自動調用Runnable 接口對象的run方法。

  實現方式和繼承方法的區別:

    實現方式的好處:避免了單繼承的侷限性.在定義線程時,建議使用實現方式. 


3. 線程資源的同步處理

     3.1 臨界資源問題

wKioL1P8Z4ayeJJcAACC3rgeySg856.jpg

     Java對於多線程的安全問題提供了專業的解決方式:  就是同步代碼塊

  synchronized(對象) {

    須要被同步的代碼

  }

          對象如同鎖,持有鎖的線程能夠在同步中執行.

          沒有持有鎖的線程及時獲取cpu的執行權,也進不去,由於沒有獲取鎖.

          同步的前提:

               1.必需要有兩個或者以上的線程

               2.必需要多個線程使用同一個鎖     

          好處:解決了多線程的安全問題

          弊端:多個線程須要判斷鎖,較爲消耗資源.     

     同步函數使用的是哪一個鎖:

           函數須要被對象調用,那麼函數都有一個所屬對象的引用.就是this.

           因此同步函數使用的鎖是this鎖.靜態函數使用的是該方法所在類的字節碼文件對象.

   懶漢式加強  

 1 class Single{  2     private Single(){}  3     private static Single s = null;  4     public static Single getInstance(){  5         if(s == null){  6             synchronized(Single.class){  7                 if(s == null)  8                     s = new Single();  9  } 10  } 11         return s; 12  } 13 }

     3.2 wait()和notify()

     3.3 避免死鎖

          多個線程相互等待對方釋放持有的鎖,而且在獲得對方鎖以前不釋放本身的鎖.(本身能寫一個死鎖代碼)    

 1 /*
 2  * 死鎖: 同步中嵌套同步容易發生  3  */
 4 
 5 public class LockDemo {  6 
 7     public static void main(String[] args) {  8         Test t = new Test(false);  9         Test t2 = new Test(true); 10         new Thread(t).start(); 11         new Thread(t2).start(); 12 
13  } 14 
15 } 16 
17 class Test implements Runnable{ 18     private boolean flag; 19     Test(boolean flag){ 20         this.flag = flag; 21  } 22     public void run(){ 23         if(flag){ 24             try{Thread.sleep(30);}catch(Exception e){} 25             synchronized(KK.oa){ 26                 System.out.println("if a"); 27                 synchronized(KK.ob){ 28                     System.out.println("if b"); 29  } 30  } 31  } 32         else{ 33             synchronized(KK.ob){ 34                 System.out.println("else b"); 35                 synchronized(KK.oa){ 36                     System.out.println("else a"); 37  } 38  } 39  } 40  } 41 } 42 
43 //自定義兩個鎖
44 class KK{ 45     static Object oa = new Object(); 46     static Object ob = new Object(); 47 }

4. 線程間通訊

     思考1: wait(),notify(),nofifyAll()用來操做線程爲何定義在Object類中?

          1.這些方法存在同步中,用來操做同步中的線程,必需要標誌它們所操做線程的只有鎖.

          2.使用這些方法必須標識出所屬同步的鎖,只有同一個鎖上被等待線程,能夠被同一個鎖上notify喚醒.不能夠對不一樣鎖中的線程進行喚醒.

          3.鎖能夠是任意對象,因此任意對象調用的方法必定定義Object類中.

     思考2:wait(),sleep()有什麼區別?

          wait():釋放資源,釋放鎖

          sleep():釋放資源,不釋放鎖 

 

 1 //線程通訊: 取mike, 取麗麗
 2 public class Demo2 {
 3 
 4     public static void main(String[] args) {
 5         Person p = new Person();
 6         new Thread(new Input(p)).start();
 7         new Thread(new Output(p)).start();
 8     }
 9 
10 }
11 
12 class Person{
13     private String name ;
14     private String sex ;
15     private boolean flag = false;
16     
17     public synchronized void set(String name, String sex){
18         //flag爲true,表示已存在
19         if(flag){
20             try {wait();}catch (InterruptedException e) {e.printStackTrace();}
21         }
22         //flag爲false,表示沒人可加
23         this.name = name;
24         this.sex = sex;
25         flag = true;
26         this.notify();
27     }
28     
29     public synchronized void out(){
30         //flag爲false,表示無人無法取
31         if(!flag){
32             try {wait();}catch (InterruptedException e) {e.printStackTrace();}
33         }
34         //flag爲true,表示有人能夠取出
35         System.out.println(toString());    
36         flag = false;
37         this.notify();        
38     }
39     
40     public String toString(){
41         return "姓名:"+name+"性別:"+sex;
42     }
43 }
44 
45 class Input implements Runnable{
46     private Person p;
47     
48     Input(Person p){
49         this.p = p;
50     }
51     
52     public void run(){
53         boolean flag = false;
54         while(true){    
55                 if(flag){
56                     p.set("likai","男");
57                     flag = false;
58                 }
59                 else{
60                     p.set("tangll","女");    
61                     flag = true;
62                 }    
63         }
64     }
65 }
66 
67 class Output implements Runnable{
68     private Person p;
69     
70     Output(Person p){
71         this.p = p;
72     }
73     
74     public void run(){
75         while(true){
76             p.out();
77         }
78     }
79 }
線程通訊Demo1    
 1 public class Demo3 {
 2 
 3     public static void main(String[] args) {
 4         Bridge b = new Bridge();
 5         for(int i=1;i<6;i++){
 6             new Thread(new Person(b,"由北向南第"+i+"人")).start();
 7         }
 8         
 9         for(int i=1;i<7;i++){
10             new Thread(new Person(b,"由南向北第"+i+"人")).start();
11         }
12     }
13 }
14 
15 class Bridge{
16     private String name;
17     private boolean flag;
18     public synchronized void UpBridge(){
19         if(flag){
20             try {
21                 wait();
22             } catch (InterruptedException e) {            
23                 e.printStackTrace();
24             }
25         }
26         flag = true;
27         
28     }
29     public synchronized void DownBridge(){
30         flag = false;
31         notifyAll();
32     }
33 }
34 
35 class Person implements Runnable{
36     private Bridge bridge;
37     private String pName;
38     
39     Person(Bridge bridge, String pName){
40         this.bridge = bridge;
41         this.pName = pName;
42     }
43 
44     public void run() {        
45         bridge.UpBridge();
46         try{
47             Thread.sleep(80);
48         }catch(InterruptedException e){}
49         bridge.DownBridge();
50         System.out.println(pName);
51     }
52     
53 }
過橋問題

生產消費者問題

     生產消費者問題--JDK1.5提供的多線程升級解決方法

          將同步synchronized替換成Lock操做.

          將Object中的wait,notify,notifyAll方法替換成Condition對象.

          該對象能夠Lock鎖進行獲取.

    該實例中,實現了本方只喚醒對方的操做.

     中止線程

          1.定義循環結束標記

               由於線程運行代碼通常都是循環,只是控制了循環便可.

          2.使用interrupt(中斷)方法: 該方法是強制結束線程的凍結狀態,使線程回到運行狀態中來.這樣就能夠操做標記結束.

          (注)stop方法和suspend方法已過期不在再使用.

          setDeamon()方法

               標記爲後臺線程, 當全部前臺前程結束後會Java虛擬機退出. 該方法必須在啓動線程前調用.   

          join()方法

               等待該線程終止, 實際上是搶奪CPU執行權.

               當A線程執行到B線程的.join()方法, A就會等待. 等B線程都執行完, A纔會執行. join能夠用來臨時加入線程執行.

          toString()

               返回該線程的字符串表示形式,包括線程名稱,優先級和線程組

          yield()

               暫停當前執行的線程對象,並執行其餘線程