1. 程式人生 > >java中的多執行緒 // 基礎

java中的多執行緒 // 基礎

java 中的多執行緒

簡介

  程序 : 指正在執行的程式,並具有一定的獨立能力,即 當硬碟中的程式進入到記憶體中執行時,就變成了一個程序

  執行緒 : 是程序中的一個執行單元,負責當前程式的執行。執行緒就是CPU通向程式的路徑

       一個程序中只有一個執行緒,單執行緒程式

       一個程序中是可以有多個執行緒的,這個應用程式是多執行緒程式

 

程式的執行分類

  分時排程

    所有執行緒輪流使用CPU 的使用權,平均分配每個執行緒佔用CPU 的時間

  搶佔式排程

    優先讓優先順序高的執行緒使用CPU,如果執行緒的優先順序相同,那麼就會隨機選擇一個執行緒(執行緒的隨機性)。

    java 使用的為搶佔式排程

   搶佔式排程簡介:

    現在的作業系統都支援多程序併發執行,比如:一邊用office ,一邊使用QQ,一邊看著視訊  等等,

    看著好像這些程式都在同一時刻執行,實際上是 CPU(中央處理器)使用搶佔式排程模式在多個執行緒間進行著高速的切換。

    對於CPU的一個核而言,某個時刻只能執行一個執行緒,而CPU 在多個執行緒之間切換的速度相對我們而言感覺要快,看上去就是在同一時刻執行。

    注意:

      多執行緒並不能提高程式的執行速度,但能夠提高程式的執行效率,讓CPU 的使用效率更高。

 

多執行緒的由來  

  jvm啟動後,必然有一個執行執行緒(路徑)從main方法開始的,一直執行到main方法結束,這個執行緒在java中稱之為主執行緒(main執行緒)。 

  若主執行緒遇到迴圈,並迴圈次數較多,則導致程式在指定的位置停留時間過長,無法馬上執行下面的程式,則需要等待迴圈結束後才能夠執行程式碼。效率慢。 

  主執行緒負責執行其中的一個迴圈,由另一個執行緒執行另一個迴圈,最終實現多部分程式碼同時執行。多執行緒之間互不影響。

 

 多執行緒的建立方式

  1、繼承 Thread 類

    建立一個類,繼承 Thread 類,並重寫Thread 類的 run 方法

    建立物件,呼叫 run 方法 ,就相當於執行其他執行緒的 main 方法。

    步驟:

      1、自定義一個類,繼承Thread 類

      2、重寫Thread 類中的 run 方法 ,設定執行緒任務

      3、建立自定義類的例項

      4、例項化 Thread 類,並傳入自定義的類

      4、呼叫start ,開啟程序。

 

    示例:

 1 1、自定義類
 2 // 建立一個類,繼承Thread
 3 public class Thread_01 extends Thread{
 4     // 重寫run 方法,在run方法中定義執行緒任務
 5     public void run(){
 6         System.out.println(Thread.currentThread().getName());
 7     }
 8 }
 9 2、main方法
10 public class ThreadDemo {
11     public static void main(String[] args) {
12         // 建立執行緒物件 t1
13         Thread_01 t1 = new Thread_01();
14         // 為了啟動Thread_01 這個執行緒,需要例項化Thread,並傳入自己的Thread_01例項
15         Thread thread = new Thread(t1);
16         // 通知CPU 要啟動執行緒
17         thread.start();
18         System.out.println(Thread.currentThread().getName());
19     }
20 }
多執行緒繼承Thread 示例

 

2、實現 Runnable 介面

     建立一個類,實現 Runnable 介面,重寫 run  方法

    步驟:

      1、自定義一個類,實現 Runnable 介面

      2、重寫run 方法,在run方法中設定執行緒任務

      3、在main 方法中,建立自定義類的例項化

      4、例項化Thread 類,並傳入自定義類的例項化

      5、呼叫start 方法,開啟程序

 1 1、自定義類,實現runnable 介面
 2 // 自定義類,實現Runnable 介面
 3 public class Runnable_01 implements Runnable{
 4     // 重寫run 方法,在run方法中設定執行緒任務
 5     @Override
 6     public void run() {
 7         System.out.println(Thread.currentThread().getName());
 8     }
 9 
10 }
11 
12 2、在main方法中,呼叫
13 public class ThreadDemo {
14     public static void main(String[] args) {
15         // 建立執行緒物件 t1
16         Runnable_01 t1 = new Runnable_01();
17         // 為了啟動Runnable_01 這個執行緒,需要例項化Thread,並傳入自己的Runnable_01例項
18         Thread thread = new Thread(t1);
19         // 通知CPU 要啟動執行緒
20         thread.start();
21         System.out.println(Thread.currentThread().getName());
22     }
23 }
多執行緒實現 Runnable 介面

    注意:

      呼叫 start 方法,開啟新執行緒。若沒有呼叫start 方法,只調用run 方法,只是呼叫了一個方法而已,並沒有開啟新執行緒

      一個執行緒只能呼叫一次start 方法,若執行緒執行結束後,不能再呼叫。

 

常用API 

 1 public class Demo {
 2     public static void main(String[] args) {
 3         // 呼叫currentThread().getName(),獲取當前執行緒的名稱
 4         System.out.println(Thread.currentThread().getName() + "123");
 5         // 呼叫currentThread().getId(),獲取當前執行緒的識別符號
 6         System.out.println(Thread.currentThread().getId());
 7         // 呼叫currentThread().getPriority(),獲取當前執行緒的優先順序
 8         System.out.println(Thread.currentThread().getPriority());
 9         // 呼叫currentThread().setName() 給當前執行緒設定新名稱
10         Thread.currentThread().setName("執行緒新名稱");
11         // 呼叫currentThread().getName(),獲取當前執行緒的名稱
12         System.out.println(Thread.currentThread().getName() + "123");
13         /**
14          * 列印結果 :main123
15          *         1
16          *         5
17          *         執行緒新名稱123
18          */
19 
20     }
21 }
執行緒API 使用示例

 

執行緒安全

  若多執行緒呼叫全域性變數時,會出現執行緒安全問題。

  即:使用java 模擬視窗賣票時,一個視窗就是一個執行緒,若同時賣票,可能會出現幾個視窗同時賣一張票,或者賣出不存在的票(就剩一張票時,兩個視窗同時賣出)

  所以,使用多執行緒時,要注意執行緒安全問題,解決執行緒安全問題有三種方式,

  方式一:同步程式碼塊

    同步程式碼塊:就是在方法塊宣告上加上 synchronized

synchronized (鎖物件) {
	可能會產生執行緒安全問題的程式碼
}

  注意: 

    同步程式碼塊中的鎖物件可以是任意的物件;但多個執行緒時,要使用同一個鎖物件才能夠保證執行緒安全。

    物件可以是this, 哪個物件呼叫,方法中的this就是哪個物件

  示例:

 1 /*
 2  * 開啟3個執行緒,同時賣100張票
 3  */
 4 public class Demo01PayTicket {
 5     public static void main(String[] args) {
 6         //建立介面的實現類物件
 7         RunnableImpl r = new RunnableImpl();
 8         //建立執行緒物件
 9         Thread t0 = new Thread(r);
10         Thread t1 = new Thread(r);
11         Thread t2 = new Thread(r);
12         //開啟多執行緒
13         t0.start();
14         t1.start();
15         t2.start();
16     }
17 }
18 
19 /*
20  * 發現程式出現了安全問題:賣出了重複的票和不存在的票
21  * 
22  * 多執行緒安全問題的第一種解決方案:使用同步程式碼塊
23  * 
24  * 注意:
25  *         程式碼塊中傳遞的鎖物件必須保證唯一,多個執行緒使用的是同一個鎖物件
26  *         鎖物件可以是任意的物件
27  * 
28  */
29 public class RunnableImpl implements Runnable{
30     
31     //定義一個共享的票源
32     private int ticket = 100;
33     //建立一個鎖物件
34     Object obj = new Object();
35 
36     @Override
37     public void run() {
38         //讓賣票重複執行
39         while(true){
40             //同步程式碼塊
41             synchronized (obj) {
42                 //判斷是否還有票
43                 if(ticket>0){
44                     
45                     //提高安全問題出現的概率,增加一個sleep
46                     try {
47                         Thread.sleep(10);
48                     } catch (InterruptedException e) {
49                         e.printStackTrace();
50                     }
51                     
52                     //進行賣票
53                     System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
54                     ticket--;
55                 }
56             }
57         }    
58     }
59 }
多執行緒 同步程式碼塊

 

  方式二:同步方法

    1、同步方法:在方法宣告上加上 synchronized

public synchronized void method(){
   	可能會產生執行緒安全問題的程式碼
} 

  同步方法中的鎖物件是 this,哪個物件呼叫,方法中的this就是哪個物件

 

    2、靜態同步方法:在方法宣告上加上 static synchronized

public static synchronized void method(){
     可能會產生執行緒安全問題的程式碼
}

  靜態同步方法中的鎖物件不是物件,是本類的 .class 檔案

   示例:

 1 /*
 2  * 開啟3個執行緒,同時賣100張票
 3  */
 4 public class Demo01PayTicket {
 5     public static void main(String[] args) {
 6         //建立介面的實現類物件
 7         RunnableImpl r = new RunnableImpl();
 8         //建立執行緒物件
 9         Thread t0 = new Thread(r);
10         Thread t1 = new Thread(r);
11         Thread t2 = new Thread(r);
12         //開啟多執行緒
13         t0.start();
14         t1.start();
15         t2.start();
16     }
17 }
18 
19 /*
20  * 發現程式出現了安全問題:賣出了重複的票和不存在的票
21  * 
22  * 多執行緒安全問題的第二種解決方案:使用同步方法
23  * 
24  * 實現步驟:
25  *         1.把訪問了共享資料的程式碼提取出來放在一個方法中
26  *         2.在方法上新增一個synchronized修飾符
27  * 
28  * 格式:
29  *     修飾符 synchronized 返回值型別 方法名(引數){
30  *         訪問了共享資料的程式碼;
31  *     }
32  * 
33  * 把選中的程式碼提取到方法中快捷鍵:alt+shift+m
34  * 
35  */
36 public class RunnableImpl implements Runnable{
37     
38     //定義一個共享的票源
39     private static int ticket = 100;
40 
41     @Override
42     public void run() {
43         //讓賣票重複執行
44         while(true){
45             payTicketStatic();
46         }
47         
48     }
49     
50     /*
51      * 靜態的同步方法,鎖物件不是this
52      * 靜態優先於非靜態載入到記憶體中,this是建立物件之後才有的
53      * 鎖物件是本類的class屬性(反射-->class檔案物件)
54      */
55     public static synchronized void payTicketStatic() {
56         synchronized (RunnableImpl.class) {
57             //判斷是否還有票
58             if(ticket>0){
59                 //提高安全問題出現的概率,增加一個sleep
60                 try {
61                     Thread.sleep(10);
62                 } catch (InterruptedException e) {
63                     e.printStackTrace();
64                 }
65                 //進行賣票
66                 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
67                 ticket--;
68             }
69         }
70     }
71 
72     /*
73      * 定義一個賣票的方法
74      * 使用synchronized修飾
75      * 使用鎖物件把方法鎖住
76      * 這個鎖物件是誰?
77      * 建立的實現類物件new RunnableImpl();
78      * 也就是this,哪個物件呼叫的方法,方法中的this就是哪個物件
79      */
80     public synchronized void payTicket() {
81         //System.out.println(this);//[email protected]
82         synchronized (this) {
83             //判斷是否還有票
84             if(ticket>0){
85                 //提高安全問題出現的概率,增加一個sleep
86                 try {
87                     Thread.sleep(10);
88                 } catch (InterruptedException e) {
89                     e.printStackTrace();
90                 }
91                 
92                 //進行賣票
93                 System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
94                 ticket--;
95             }
96         }
97     }
98 }
多執行緒 同步方法 synchronized

 

  方式三:使用lock 鎖

    Lock 是介面,  ReentrantLock 是Lock 的實現類

    API

      

    呼叫

      1、建立ReentrantLock 物件

      2、在可能產生安全問題程式碼前呼叫 lock() 方法,獲得鎖

      3、呼叫unlock()方法,解鎖

    

  ReentrantLock rl = new ReentrantLock();
  //獲得鎖
  rl.LOCK
  可能會產生執行緒安全問題的程式碼
  Rl.unlock

  示例:

 1 /*
 2  * 開啟3個執行緒,同時賣100張票
 3  */
 4 public class Demo01PayTicket {
 5     public static void main(String[] args) {
 6         //建立介面的實現類物件
 7         RunnableImpl r = new RunnableImpl();
 8         //建立執行緒物件
 9         Thread t0 = new Thread(r);
10         Thread t1 = new Thread(r);
11         Thread t2 = new Thread(r);
12         //開啟多執行緒
13         t0.start();
14         t1.start();
15         t2.start();
16     }
17 }
18 
19 /*
20  * 發現程式出現了安全問題:賣出了重複的票和不存在的票
21  * 
22  * 多執行緒安全問題的第三種解決方案:使用Lock鎖
23  * java.util.concurrent.locks.Lock介面
24  * Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。
25  * JDK1.5之後出現的新特性
26  * 
27  * 實現步驟:
28  *     1.在成員位置建立一個ReentrantLock物件
29  *     2.在訪問了共享資料的程式碼前,呼叫lock方法,獲取鎖物件
30  *     3.在訪問了共享資料的程式碼後,呼叫unlock方法,釋放鎖物件
31  * 
32  */
33 public class RunnableImpl implements Runnable{
34     
35     //定義一個共享的票源
36     private int ticket = 100;
37     //1.在成員位置建立一個ReentrantLock物件
38     Lock l = new ReentrantLock();
39     
40     @Override
41     public void run() {
42         //讓賣票重複執行
43         while(true){
44             //2.在訪問了共享資料的程式碼前,呼叫lock方法,獲取鎖物件
45             l.lock();
46             try {
47                 //可能會出現安全問題的程式碼
48                 //判斷是否還有票
49                 if(ticket>0){
50                     //提高安全問題出現的概率,增加一個sleep
51                     Thread.sleep(10);
52                     //進行賣票
53                     System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票");
54                     ticket--;
55                 }
56                 
57             } catch (Exception e) {
58                 //異常的處理邏輯
59                 System.out.println(e);
60             } finally {
61                 //一定會執行的程式碼,資源釋放
62                 //3.在訪問了共享資料的程式碼後,呼叫unlock方法,釋放鎖物件
63                 l.unlock();//無論是否異常,都會釋放掉鎖物件
64             }
65         }
66         
67     }
68 }
多執行緒 lock 鎖 示例

 

多執行緒執行緒圖

  執行緒的五種狀態:

    新建狀態-->執行狀態-->死亡(結束)狀態

                        阻塞狀態

          凍結狀態(休眠/無限等待)

    新建 :剛創建出來的執行緒,即 new Thread();

    阻塞 :沒有搶到CPU,在等待CPU呼叫

    執行 :呼叫run 方法,在執行狀態

    死亡 :方法結束,呼叫完成

    休眠 :呼叫sleep() 方法,進入休眠狀態

                          sleep 是Thread 的一個函式

                          sleep 指佔用CPU 不工作,其他執行緒無法進入。即:sleep不會讓出系統資源;

    無限等待 : 呼叫wait() 方法,未被喚醒

                          wait 是object 的一個函式,需要呼叫notify() 來喚醒

                          wait 指不佔用CPU 不工作,其他執行緒可以進入。即:wait是進入執行緒等待池中等待,讓出系統資源。