1. 程式人生 > 其它 >Java多執行緒(三)執行緒的同步

Java多執行緒(三)執行緒的同步

JDK中用Thread.State類定義了執行緒的幾種狀態

要想實現多執行緒,必須在主執行緒中建立新的執行緒物件。Java語言使用Thread類及其子類的物件來表示執行緒,在它的一個完整的生命週期中通常要經歷如下的五種狀態: 新建: 當一個Thread類或其子類的物件被宣告並建立時,新生的執行緒物件處於新建狀態。 就緒:處於新建狀態的執行緒被start()後,將進入執行緒佇列等待CPU時間片,此時它已具備了執行的條件,只是沒分配到CPU資源。或者使用yield()方法。 執行:當就緒的執行緒被排程並獲得CPU資源時,便進入執行狀態, run()方法定義了執行緒的操作和功能。當呼叫sleep() join() wait() suspend()方法和等待同步鎖時會由執行到阻塞。 阻塞
:在某種特殊情況下,被人為掛起或執行輸入輸出操作時,讓出 CPU 並臨時中止自己的執行,進入阻塞狀態。當sleep()時間到、join()結束、獲取同步鎖、notify()/notifyAll()、resume()方法是由阻塞轉為就緒。 死亡:執行緒完成了它的全部工作或執行緒被提前強制性地中止或出現異常導致結束。

執行緒的同步:通過同步機制來解決執行緒安全問題

使用synchronized--程式碼同步塊來實現執行緒同步

 1 /**
 2  * 例子:建立三個視窗賣票,總票數為100張.使用實現Runnable介面的方式
 3  *
 4  * 1.問題:賣票過程中,出現了重票、錯票 -->出現了執行緒的安全問題
5 * 2.問題出現的原因:當某個執行緒操作車票的過程中,尚未操作完成時,其他執行緒參與進來,也操作車票。 6 * 3.如何解決:當一個執行緒a在操作ticket的時候,其他執行緒不能參與進來。直到執行緒a操作完ticket時,其他 7 * 執行緒才可以開始操作ticket。這種情況即使執行緒a出現了阻塞,也不能被改變。 8 * 9 * 10 * 4.在Java中,我們通過同步機制,來解決執行緒的安全問題。 11 * 12 * 方式一:同步程式碼塊 13 * 14 * synchronized(同步監視器){ 15 * //需要被同步的程式碼
16 * 17 * } 18 * 說明:1.操作共享資料的程式碼,即為需要被同步的程式碼。 -->不能包含程式碼多了,也不能包含程式碼少了。 19 * 2.共享資料:多個執行緒共同操作的變數。比如:ticket就是共享資料。 20 * 3.同步監視器,俗稱:鎖。任何一個類的物件,都可以充當鎖。 21 * 要求:多個執行緒必須要共用同一把鎖。 22 * 23 * 補充:在實現Runnable介面建立多執行緒的方式中,我們可以考慮使用this充當同步監視器。 24 * 方式二:同步方法。 25 * 如果操作共享資料的程式碼完整的宣告在一個方法中,我們不妨將此方法宣告同步的。 26 * 27 * 28 * 5.同步的方式,解決了執行緒的安全問題。---好處 29 * 操作同步程式碼時,只能有一個執行緒參與,其他執行緒等待。相當於是一個單執行緒的過程,效率低。 ---侷限性 30 * 31 */ 32 class Window1 implements Runnable{ 33 34 private int ticket = 100; 35 // Object obj = new Object(); 36 // Dog dog = new Dog(); 37 @Override 38 public void run() { 39 // Object obj = new Object(); 40 while(true){ 41 synchronized (this){//此時的this:唯一的Window1的物件,若使用繼承的方式建立執行緒則不能用this //方式二:synchronized (dog) { 42 43 if (ticket > 0) { 44 45 try { 46 Thread.sleep(100); 47 } catch (InterruptedException e) { 48 e.printStackTrace(); 49 } 50 51 System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket); 52 53 54 ticket--; 55 } else { 56 break; 57 } 58 } 59 } 60 } 61 } 62 63 public class WindowTest1 { 64 public static void main(String[] args) { 65 Window1 w = new Window1(); 66 67 Thread t1 = new Thread(w); 68 Thread t2 = new Thread(w); 69 Thread t3 = new Thread(w); 70 71 t1.setName("視窗1"); 72 t2.setName("視窗2"); 73 t3.setName("視窗3"); 74 75 t1.start(); 76 t2.start(); 77 t3.start(); 78 } 79 }

使用同步方法解決Runnable介面的執行緒安全

 1 package com.atguigu.java;
 2 
 3 /**
 4  * 使用同步方法解決實現Runnable介面的執行緒安全問題
 5  *
 6  *
 7  *  關於同步方法的總結:
 8  *  1. 同步方法仍然涉及到同步監視器,只是不需要我們顯式的宣告。
 9  *  2. 非靜態的同步方法,同步監視器是:this
10  *     靜態的同步方法,同步監視器是:當前類本身
11  *
12  */
13 
14 
15 class Window3 implements Runnable {
16 
17     private int ticket = 100;
18 
19     @Override
20     public void run() {
21         while (true) {
22 
23             show();
24         }
25     }
26 
27     private synchronized void show(){//同步監視器:this
28         //synchronized (this){
29 
30             if (ticket > 0) {
31 
32                 try {
33                     Thread.sleep(100);
34                 } catch (InterruptedException e) {
35                     e.printStackTrace();
36                 }
37 
38                 System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
39 
40                 ticket--;
41             }
42         //}
43     }
44 }
45 
46 
47 public class WindowTest3 {
48     public static void main(String[] args) {
49         Window3 w = new Window3();
50 
51         Thread t1 = new Thread(w);
52         Thread t2 = new Thread(w);
53         Thread t3 = new Thread(w);
54 
55         t1.setName("視窗1");
56         t2.setName("視窗2");
57         t3.setName("視窗3");
58 
59         t1.start();
60         t2.start();
61         t3.start();
62     }
63 
64 }

使用同步方法解決實現Runnable介面的執行緒安全問題

 1 /**
 2  * 使用同步方法處理繼承Thread類的方式中的執行緒安全問題
 3  */
 4 class Window4 extends Thread {
 5 
 6 
 7     private static int ticket = 100;
 8 
 9     @Override
10     public void run() {
11 
12         while (true) {
13 
14             show();
15         }
16 
17     }
18     private static synchronized void show(){//同步監視器:Window4.class
19         //private synchronized void show(){ //同步監視器:t1,t2,t3。此種解決方式是錯誤的
20         if (ticket > 0) {
21 
22             try {
23                 Thread.sleep(100);
24             } catch (InterruptedException e) {
25                 e.printStackTrace();
26             }
27 
28             System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
29             ticket--;
30         }
31     }
32 }
33 
34 
35 public class WindowTest4 {
36     public static void main(String[] args) {
37         Window4 t1 = new Window4();
38         Window4 t2 = new Window4();
39         Window4 t3 = new Window4();
40 
41 
42         t1.setName("視窗1");
43         t2.setName("視窗2");
44         t3.setName("視窗3");
45 
46         t1.start();
47         t2.start();
48         t3.start();
49 
50     }
51 }

同步機制中的鎖

在《Thinking in Java》中,是這麼說的:對於併發工作,你需要某種方式來防止兩個任務訪問相同的資源(其實就是共享資源競爭)。 防止這種衝突的方法就是當資源被一個任務使用時,在其上加鎖。第一個訪問某項資源的任務必須鎖定這項資源,使其他任務在其被解鎖之前,就無法訪問它了,而在其被解鎖之時,另一個任務就可以鎖定並使用它了。

synchronized中的鎖

任意物件都可以作為同步鎖。所有物件都自動含有單一的鎖(監視器)。

同步方法的鎖:靜態方法(類名.class)、非靜態方法(this)

同步程式碼塊:自己指定,可以指定為this或類名.class

注意:

1. 必須確保使用同一個資源的多個執行緒共用一把鎖,這個非常重要,否則就無法保證共享資源的安全 2. 一個執行緒類中的所有靜態方法共用同一把鎖(類名.class),所有非靜態方法共用同一把鎖(this),同步程式碼塊(指定需謹慎)

同步的範圍

1、如何找問題,即程式碼是否存線上程安全?(非常重要) (1)明確哪些程式碼是多執行緒執行的程式碼 (2)明確多個執行緒是否有共享資料 (3)明確多執行緒執行程式碼中是否有多條語句操作共享資料 2、如何解決呢?(非常重要) 對多條操作共享資料的語句,只能讓一個執行緒都執行完,在執行過程中,其他執行緒不可以參與執行。即所有操作共享資料的這些語句都要放在同步範圍中。 3、切記: 範圍太小:沒鎖住所有有安全問題的程式碼 範圍太大:沒發揮多執行緒的功能。

釋放鎖的操作

1. 當前執行緒的同步方法、同步程式碼塊執行結束。 2. 當前執行緒在同步程式碼塊、同步方法中遇到break、return終止了該程式碼塊或方法的繼續執行。 3. 當前執行緒在同步程式碼塊、同步方法中出現了未處理的Error或Exception,導致異常結束。 4. 當前執行緒在同步程式碼塊、同步方法中執行了執行緒物件的wait()方法,當前執行緒暫停,並釋放鎖。