java 多執行緒-2

Noah996 發表於 2020-09-12

七、執行緒生命週期

沒錯,執行緒也是有生命週期的。就好像人類有出生、兒童、青年、中年、晚年、死亡一般。下面是執行緒的生命週期圖:
java 多執行緒-2

八、執行緒的安全問題

所謂執行緒不安全【併發問題】,舉個例子來說,如賣票,會出現重票、錯票等現象,這就是執行緒不安全的。

並行:多個CPU同時執行多個任務。比如:多個人同時做不同的事

併發:一個CPU(採用時間片)同時執行多個任務。比如:秒殺、多個人做同一件事

如:

/**
 * 建立三個視窗買票,共100張票。用Runnable介面實現
 */
public class RunMainRunnable {
    public static void main(String[] args) {
        Window window = new Window();
        // 建立三個執行緒
        Thread win1 = new Thread(window);
        Thread win2 = new Thread(window);
        Thread win3 = new Thread(window);
        // 設定執行緒的名字
        win1.setName("視窗一:");
        win2.setName("視窗二:");
        win3.setName("視窗三:");
        // 啟動執行緒
        win1.start();
        win2.start();
        win3.start();
    }
}

class Window implements Runnable{
    private int ticket = 100; // 定義100張票
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                try {
                    Thread.sleep(100); // 呼叫此方法,讓效果明顯一點
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"獲取到了第"+ticket+"票");
                ticket--;
            }else{
                break;
            }
        }
    }
}

執行結果:出現了重票
java 多執行緒-2

問題分析:

之所以會出現重票、錯票等問題,就是因為出現了執行緒不安全。【假設此時ticket=7】在if判斷中,如當視窗一獲得cpu後,首先判斷ticket>0會真,執行列印語句輸出“視窗一:獲取到了第7票”,此時視窗二獲得cpu【注意:視窗一併未執行“ticket--;”,ticket依然為7】,判斷ticket>0為真,執行列印語句輸出“視窗二:獲取到了第7票”,此時視窗三獲得cpu【注意:視窗二並未執行“ticket--;”,ticket依然為7】,判斷ticket>0為真,執行列印語句輸出“視窗三:獲取到了第7票”。便會出現重票。這在實際生活當中,肯定是不允許的,那我們該如何解決執行緒的安全問題呢?

九、同步機制

在Java中,我們通過同步機制,來解決執行緒的安全問題。

方式一:同步程式碼塊

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

}

說明:

  1. 什麼是同步的程式碼?
    • 操作共享資料的程式碼,即為需要被同步的程式碼。
  2. 什麼是共享資料?
    • 多個執行緒共同操作的變數。比如:ticket就是共享資料。
  3. 什麼是同步監視器
    • 俗稱:鎖。任何一個類的物件,都可以充當鎖。
    • 要求:多個執行緒必須要共用同一把鎖。

方式二:同步方法

 如果操作共享資料的程式碼完整的宣告在一個方法中,我們不妨將此方法宣告為同步的。

總結

好處:同步的方式,解決了執行緒的安全問題。

壞處:操作同步程式碼時,只能有一個執行緒參與,其他執行緒等待。相當於是一個單執行緒的過程,效率低。 ---侷限性

eg1:方式一:同步程式碼塊

/**
 * 建立三個視窗買票,共100張票。用Runnable介面實現
 */
public class RunMainRunnable {
    public static void main(String[] args) {
        Window window = new Window();
        // 建立三個執行緒
        Thread win1 = new Thread(window);
        Thread win2 = new Thread(window);
        Thread win3 = new Thread(window);
        // 設定執行緒的名字
        win1.setName("視窗一:");
        win2.setName("視窗二:");
        win3.setName("視窗三:");
        // 啟動執行緒
        win1.start();
        win2.start();
        win3.start();
    }
}

/**
 * 方式一;同步程式碼塊
 */
class Window implements Runnable{
    private int ticket = 100; // 定義100張票
    private Object object = new Object();
    @Override
    public void run() {
        while (true) {
           synchronized (object){  // 同步程式碼塊
               if (ticket > 0) {
                   try {
                       Thread.sleep(100); // 呼叫此方法,讓效果明顯一點
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+"獲取到了第"+ticket+"票");
                   ticket--;
               }else{
                   break;
               }
           }
        }
    }
}

eg2:方式二:同步方法

/**
 * 建立三個視窗買票,共100張票。用Runnable介面實現
 */
public class RunMainRunnable {
    public static void main(String[] args) {
        Window window = new Window();
        // 建立三個執行緒
        Thread win1 = new Thread(window);
        Thread win2 = new Thread(window);
        Thread win3 = new Thread(window);
        // 設定執行緒的名字
        win1.setName("視窗一:");
        win2.setName("視窗二:");
        win3.setName("視窗三:");
        // 啟動執行緒
        win1.start();
        win2.start();
        win3.start();
    }
}

/*
方式二;同步方法
* */
class Window implements Runnable{
    private int ticket = 100; // 定義100張票
    @Override
    public void run() {
        while (true) {
            show();// 呼叫同步方法
            if (ticket == 0) {
                break; // 用來結束迴圈
            }
        }
    }

    // 定義一個私有方法
    private synchronized void show() { // 加synchronized關鍵字,使其成為一位同步方法。同步監視器或鎖即為:this
        if (ticket > 0) {
            try {
                Thread.sleep(100); // 呼叫此方法,讓效果明顯一點
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"獲取到了第"+ticket+"票");
            ticket--;
        }
    }
}

最新文章