1. 程式人生 > 其它 >Java多執行緒(四)

Java多執行緒(四)

技術標籤:Java基礎java多執行緒jdk1.8

目錄

四、執行緒的同步

4.1 執行緒同步介紹

4.2 執行緒同步實現

四、執行緒的同步

4.1 執行緒同步介紹

  • 場景案例

    例如電影院有三臺自動售票機售賣100張電影票,不考慮實際情況,票號從100號依次減少,售票過程包括出票和自減兩個步驟,先自減再出票,當其中一臺機器A自減結束未完成出票的操作,另一臺機器B執行了自減操作,這樣導致兩臺機器均售出了相同的票(重票、漏票),再者,剩餘最後一張票時,三臺機器同時執行自減操作,這樣三臺售賣機可能賣出0號票、-1號票的情況(錯票)。所以,當一臺售票機執行售票操作(自減和出票),不允許其他機器同時執行售票操作(即執行緒同步),三臺售票機相當於三條執行緒,而100張電影票則是共享資源,且實際情況下,出票和自減均是一瞬間的事情,所以從感官上是三臺機器同時操作。

  • 原因分析

    當多條語句在操作執行緒共享資料時,一個執行緒對多條語句只執行其中一部分,還沒有執行完成,另一個執行緒參與進來,導致共享資料錯誤,即發生執行緒安全問題。

  • 解決方案

    對於多條操作共享資料的語句,只允許其中一個執行緒都執行完,在執行過程中,不允許其他的執行緒參與執行,哪怕正在執行中的執行緒處於堵塞狀態,也不允許其他執行緒去執行。

  • 執行緒同步

    當執行緒共享了相同的資源,在某些時刻同時讀寫該資源時,可能會產生衝突,從而導致執行緒的安全問題,因此一次只能允許其中一條執行緒來訪問共享資源,即執行緒同步。

4.2 執行緒同步實現

4.2.1 場景介紹

  • 需求描述

準備100張票作為共享資源,三臺自動售票機進行售票,即啟動三個執行緒去消費100張電影票。

  • 繼承Thread方式

class Window1 extends Thread {
  private static Integer ticket = 100;
  @Override
  public void run() {
    while (true) {
      if (ticket > 0) {
        System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
        ticket--;
       } else {
        break;
       }
     }
   }
}
Window1 w1 = new Window1();
Window1 w2 = new Window1();
Window1 w3 = new Window1();
​
w1.setName("視窗1");
w2.setName("視窗2");
w3.setName("視窗3");
​
w1.start();
w2.start();
w3.start();
  • 實現Runnable介面

class Window2 implements Runnable {
  private Integer ticket = 100;
  @Override
  public void run() {
    while (true) {
      if (ticket > 0) {
        System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
        ticket--;
       } else {
        break;
       }
     }
   }
}
Window2 w = new Window2();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
​
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
​
t1.start();
t2.start();
t3.start();
  • 錯誤現象

image-20210121231501254

4.2.2 Synchronized使用

  • 同步程式碼塊

// 格式
synchronized(同步監視器){
  // 需要被同步的業務程式碼程式
}

1.操作共享資料的程式程式碼,即是需要被同步的程式碼,不能包含程式碼多了,也不能包含程式碼少了;

2.共享資料,即多個執行緒可以共同操作的變數,例如100張電影票;

3.同步監視器,俗稱鎖,任何一個物件都可以充當鎖,潛在的要求就是多個執行緒物件必須共用一把鎖,所以推薦繼承Thread類的的子類,使用子類的物件(類名.class),實現Runnable介面的子類,使用當前子類物件本身(this)。

  • 繼承Thread方式

private static Integer ticket = 100;
while (true) {
  synchronized (Window3.class) {
    if (ticket > 0) {
      System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
      ticket--;
     } else {
      break;
     }
   }
}
}
  • 實現Runnable介面

private Integer ticket = 100;
​
@Override
public void run() {
​
  while (true) {
    synchronized (this) {
      if (ticket > 0) {
        System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
        ticket--;
       } else {
        break;
       }
​
     }
   }
}
  • 同步方法

// synchronized還可以放在方法宣告中,表示整個方法為同步方法。 
private static synchronized void show();
private synchronized void show();
// 在方法的修飾詞中加上synchronized關鍵字

1.靜態方法,同步方法的同步監視器是類名.class;

2.非靜態方法,同步方法的同步監視器是this

  • 繼承Thread方式

private static synchronized void show() {
  if (ticket > 0) {
    System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
    ticket--;
   }
}
  • 實現Runnable介面

private synchronized void show() {
  if (ticket > 0) {
    System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
    ticket--;
   }
}

注意,這裡一個使用的是靜態的同步方法,一個使用的非靜態的同步方法。

總結

優點:同步的方式,解決了執行緒的安全問題 缺點:操作同步程式碼,只能一個執行緒參與,其他執行緒等待,相當於是單執行緒操作,效率低

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