Java小白入門 ———— 多執行緒之間實現同步(二)

2020-10-22 11:00:11

Java小白入門 ———— 多執行緒之間實現同步(二)

一. 什麼是執行緒安全:

為什麼會存線上程安全問題:

當多個執行緒同時共用同一全域性變數或靜態變數時,在做讀操作時不會傳送資料衝突,而在做寫操作時可能會傳送資料衝突問題,就會出現執行緒安全問題。

二. 案例:

背景: 某工廠需拖運100T貨物,找了3家託運公司,用多執行緒模擬託運情況。

程式碼如下:

/**
 * @author MuXin
 * @date 2020/10/21 16:11
 *
 *  某工廠需拖運100T貨物,找了3家託運公司,用多執行緒模擬託運情況。
 */
public class ThreadDemo {

    public static void main(String[] args) {
        ThreadGoods threadGoods = new ThreadGoods();
        Thread threadA = new Thread(threadGoods, "A公司");
        Thread threadB = new Thread(threadGoods, "B公司");
        Thread threadC = new Thread(threadGoods, "C公司");
        threadA.start();
        threadB.start();
        threadC.start();
    }

    static class ThreadGoods implements Runnable {

        //貨物總量
        private int goods = 100;

        @Override
        public void run() {
            while (goods > 0) {
                try {
                    //等待0.1s
                    Thread.sleep(100);
                } catch (Exception e) {
                }
                //執行運貨操作
                System.out.println(Thread.currentThread().getName()+",搬運第"+(101-goods)+"T貨物");
                goods--;
            }
        }
    }

}

輸出結果:
執行結果
當多個執行緒共用同一個全域性成員變數時,做寫的操作可能會發生資料衝突問題。

三. 執行緒安全解決方案:

1. 使用同步程式碼塊

將可能出現問題的程式碼塊包裹起來。

/**
 * @author MuXin
 * @date 2020/10/21 16:11
 * <p>
 * 某工廠需拖運100T貨物,找了3家託運公司,用多執行緒模擬託運情況。
 */
public class ThreadDemo {

    public static void main(String[] args) {
        ThreadGoods threadGoods = new ThreadGoods();
        Thread threadA = new Thread(threadGoods, "A公司");
        Thread threadB = new Thread(threadGoods, "B公司");
        Thread threadC = new Thread(threadGoods, "C公司");
        threadA.start();
        threadB.start();
        threadC.start();
    }

    static class ThreadGoods implements Runnable {
        //自定義多執行緒同步鎖
        private Object mutex = new Object();

        //貨物總量
        private int goods = 100;

        @Override
        public void run() {

            while (goods > 0) {
                try {
                    //等待0.1s
                    Thread.sleep(100);
                } catch (Exception e) {
                }

                synchronized (mutex) {
                    if(goods > 0) {
                        try {
                            //等待0.1s
                            Thread.sleep(100);
                        } catch (Exception e) {

                        }
                        //執行運貨操作
                        System.out.println(Thread.currentThread().getName() + ",搬運第" + (101 - goods) + "T貨物");
                        goods--;
                    }
                }

            }
        }
    }
}

執行結果:
程式碼運行結果

2. 使用同步函數(synchronized鎖)

在方法上修飾synchronized稱為同步函數;
synchronized使用的鎖是this鎖。

/**
 * @author MuXin
 * @date 2020/10/21 16:11
 * <p>
 * 某工廠需拖運100T貨物,找了3家託運公司,用多執行緒模擬託運情況。
 */
public class ThreadGoods implements Runnable {
    //貨物總量
    private int goods = 100;
    //自定義多執行緒同步鎖
    private Object mutex = new Object();

    @Override
    public void run() {

        while (goods > 0) {
            try {
                //等待0.1s
                Thread.sleep(100);
            } catch (Exception e) {
            }
            moveGoods();
        }
    }

    public synchronized void moveGoods() {
        if (goods > 0) {
            try {
                //等待0.1s
                Thread.sleep(100);
            } catch (Exception e) {

            }
            //執行運貨操作
            System.out.println(Thread.currentThread().getName() + ",搬運第" + (101 - goods) + "T貨物");
            goods--;
        }
    }
}

執行結果:
執行結果

3. 靜態同步函數

在方法上加static關鍵字,使用synchronized關鍵詞修飾,或者使用類.class檔案;
靜態同步函數使用的鎖是該函數所屬位元組碼檔案物件。

/**
 * @author MuXin
 * @date 2020/10/21 16:11
 * <p>
 * 某工廠需拖運100T貨物,找了3家託運公司,用多執行緒模擬託運情況。
 */
public class ThreadGoods implements Runnable {
    //貨物總量
    private int goods = 100;
    //自定義多執行緒同步鎖
    private Object mutex = new Object();

    @Override
    public void run() {

        while (goods > 0) {
            try {
                //等待0.1s
                Thread.sleep(100);
            } catch (Exception e) {
            }
            moveGoods();
        }
    }

    public  void moveGoods() {
        synchronized (ThreadGoods.class) {
            if (goods > 0) {
                try {
                    //等待0.1s
                    Thread.sleep(100);
                } catch (Exception e) {

                }
                //執行運貨操作
                System.out.println(Thread.currentThread().getName() + ",搬運第" + (101 - goods) + "T貨物");
                goods--;
            }
        }
    }
}

執行結果:
執行結果

四. 多執行緒死鎖:

1. 什麼是多執行緒死鎖:

在同步中巢狀同步,俗稱套娃,導致鎖無法釋放。

2. 如何避免死鎖:

1. 加鎖順序(執行緒按照一定的順序加鎖)
2. 加鎖時限(執行緒嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己佔有的鎖)
3. 死鎖檢測

五. 常見面試題:

1. 什麼是執行緒安全

當多個執行緒同時共用,同一個全域性變數或靜態變數,做寫的操作時,可能會發生資料衝突問題,也就是執行緒安全問題。

2. 如何解決多執行緒之間的執行緒安全問題?

1. 使用同步程式碼塊;
2. 使用 synchronized 鎖;
3. 使用靜態同步函數。

3. 為什麼使用執行緒同步或使用鎖之後能解決執行緒安全問題?

當會發生資料衝突問題時,使得同一時間只能執行一個執行緒,其他執行緒處於阻塞狀態,在執行完之後釋放鎖,交給下一個執行緒執行。

4. 什麼是多執行緒之間的同步?

在多執行緒情況下,當多個執行緒共用統一資源時,不會受到其他執行緒的影響。	

5. 什麼是同步程式碼塊?

就是將可能出現執行緒安全的程式碼包裹起來,使得同一時間只能有一個執行緒執行被包裹的程式碼,其他執行緒處於阻塞狀態。	

6. 同步程式碼塊和同步函數的區別?

同步程式碼塊是自定義鎖(明鎖)
同步函數是使用的 this 鎖

7. 同步函數和靜態同步函數的區別?/ 例如現在一個靜態方法和一個非靜態靜態怎麼實現同步?

同步函數使用的 this 鎖;
靜態同步函數使用的是位元組碼檔案,即類.class

8. 什麼是死鎖?

同步中巢狀同步,俗稱套娃。