天天看點

Java多線程之間實作同步

一、課程目标

了解線程安全?

synchronized用法

死鎖

二、什麼是線程安全?

2.1 為什麼有線程安全問題?

當多個線程同時共享,同一個全局變量或靜态變量,做寫的操作時,可能會發生資料沖突問題,也就是線程安全問題。但是做讀操作是不會發生資料沖突問題。

案例:需求現在有100張火車票,有兩個視窗同時搶火車票,請使用多線程模拟搶票效果。

代碼:運作結果:

public class ThreadTrain1 implements Runnable {
    // 這是貨票總票數,多個線程會同時共享資源
    private int trainCount = 100;

    @Override
    public void run() {
        while (trainCount > 0) {// 循環是指線程不停的去賣票
            try {
                // 等待100毫秒
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            sale();
        }
    }

    public void sale() {
        if (trainCount > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
            trainCount--;
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        ThreadTrain1 threadTrain = new ThreadTrain1(); // 定義 一個執行個體
        Thread thread1 = new Thread(threadTrain, "一号視窗");
        Thread thread2 = new Thread(threadTrain, "二号視窗");
        thread1.start();
        thread2.start();
    }
}
      
Java多線程之間實作同步

一号視窗和二号視窗同時出售火車第一張和第七張,部分火車票會重複出售。

結論發現,多個線程共享同一個全局成員變量時,做寫的操作可能會發生資料沖突問題。

三、線程安全解決辦法:

問:如何解決多線程之間線程安全問題?

      

答:使用多線程之間同步或使用鎖(lock)。

問:為什麼使用線程同步或使用鎖能解決線程安全問題呢?

答:将可能會發生資料沖突問題(線程不安全問題),隻能讓目前一個線程進行執行。代碼執行完成後釋放鎖,然後才能讓其他線程進行執行。這樣的話就可以解決線程不安全問題。

問:什麼是多線程之間同步?

答:當多個線程共享同一個資源時,不會受到其他線程的幹擾。

3.1、使用同步代碼塊

什麼是同步代碼塊?

答:就是将可能會發生線程安全問題的代碼,給包括起來。

synchronized(同一個資料){

可能會發生線程沖突問題

}

代碼樣例:

private Object mutex = new Object();// 自定義多線程同步鎖

public void sale() {
    synchronized (mutex) {
        if (trainCount > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + ",出售 第" +
                    (100 - trainCount + 1) + "張票.");
            trainCount--;
        }
    }
}
      

3.2、同步函數

什麼是同步函數?

答:在方法上修飾synchronized 稱為同步函數

代碼樣例

public synchronized void sale() {
    if (trainCount > 0) {
        try {
            Thread.sleep(40);
        } catch (Exception e) {
        }
        System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
        trainCount--;
    }
}
      

同步函數用的是什麼鎖?

答:同步函數使用this鎖。

證明方式: 一個線程使用同步代碼塊(this明鎖),另一個線程使用同步函數。如果兩個線程搶票不能實作同步,那麼會出現資料錯誤。

代碼:

public class ThreadTrain5 implements Runnable {
    // 這是貨票總票數,多個線程會同時共享資源
    private int trainCount = 100;
    public boolean flag = true;
    private Object mutex = new Object();

    @Override
    public void run() {
        if (flag) {
            while (true) {
                synchronized (mutex) {
                    if (trainCount > 0) {
                        try {
                            Thread.sleep(40);
                        } catch (Exception e) {
                        }
                        System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
                        trainCount--;
                    }
                }
            }
        } else {
            while (true) {
                sale();
            }
        }
    }

    public synchronized void sale() {
        if (trainCount > 0) {
            try {
                Thread.sleep(40);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
            trainCount--;
        }
    }
}

public class ThreadDemo5 {
    public static void main(String[] args) throws InterruptedException {
        ThreadTrain5 threadTrain = new ThreadTrain5(); // 定義 一個執行個體
        Thread thread1 = new Thread(threadTrain, "一号視窗");
        Thread thread2 = new Thread(threadTrain, "二号視窗");
        thread1.start();
        Thread.sleep(40);
        threadTrain.flag = false;
        thread2.start();
    }
}
      

3.3、靜态同步函數

答:什麼是靜态同步函數?

方法上加上static關鍵字,使用synchronized 關鍵字修飾 或者使用類.class檔案。

靜态的同步函數使用的鎖是該函數所屬位元組碼檔案對象

可以用 getClass方法擷取,也可以用目前類名.class 表示。

代碼樣例:

synchronized (ThreadTrain.class){
    System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
    trainCount--;
    try {
        Thread.sleep(100);
    } catch (Exception e) {
    }
}
      

總結:

synchronized 修飾方法使用鎖是目前this鎖。

synchronized 修飾靜态方法使用鎖是目前類的位元組碼檔案

四、多線程死鎖

4.1、什麼是多線程死鎖?

答:同步中嵌套同步,導緻鎖無法釋放

代碼:

class ThreadTrain6 implements Runnable {
    // 這是貨票總票數,多個線程會同時共享資源
    private int trainCount = 100;
    public boolean flag = true;
    private Object mutex = new Object();

    @Override
    public void run() {
        if (flag) {
            while (true) {
                synchronized (mutex) {
                    // 鎖(同步代碼塊)在什麼時候釋放? 代碼執行完, 自動釋放鎖.
                    // 如果flag為true 先拿到 obj鎖,在拿到this 鎖、 才能執行。
                    // 如果flag為false先拿到this,在拿到obj鎖,才能執行。
                    // 死鎖解決辦法:不要在同步中嵌套同步。
                    sale();
                }
            }
        } else {
            while (true) {
                sale();
            }
        }
    }

    public synchronized void sale() {
        synchronized (mutex) {
            if (trainCount > 0) {
                try {
                    Thread.sleep(40);
                } catch (Exception e) {
                }
                System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
                trainCount--;
            }
        }
    }
}

public class DeadlockThread {
    public static void main(String[] args) throws InterruptedException {
        ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個執行個體
        Thread thread1 = new Thread(threadTrain, "一号視窗");
        Thread thread2 = new Thread(threadTrain, "二号視窗");
        thread1.start();
        Thread.sleep(40);
        threadTrain.flag = false;
        thread2.start();
    }
}
      

五、練習題

5.1、設計4個線程,其中兩個線程每次對j增加1,另外兩個線程對j每次減少1。寫出程式。

六、面試題

問:什麼是多線程安全?

答:當多個線程同時共享同一個全局變量或靜态變量,做寫的操作時,可能會發生資料沖突問題,也就是線程安全問題。做讀操作是不會發生資料沖突問題。

問:如何解決多線程之間線程安全問題?

答:使用多線程之間同步或使用鎖(lock)。

問:為什麼使用線程同步或使用鎖能解決線程安全問題呢?

答:将可能會發生資料沖突問題(線程不安全問題),隻能讓目前一個線程進行執行。被包裹的代碼執行完成後釋放鎖,讓後才能讓其他線程進行執行。這樣的話就可以解決線程不安全問題。

問:什麼是多線程之間同步?

答:當多個線程共享同一個資源,不會受到其他線程的幹擾。

問:什麼是同步代碼塊?

答:就是将可能會發生線程安全問題的代碼,給包括起來。隻能讓目前一個線程進行執行,被包裹的代碼執行完成之後才能釋放所,讓後才能讓其他線程進行執行。

問:多線程同步的分類?

1.使用同步代碼塊?

synchronized(同一個資料){

可能會發生線程沖突問題

}

private Object mutex = new Object();// 自定義多線程同步鎖

public void sale() {
    synchronized (mutex) {
        if (trainCount > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
            trainCount--;
        }
    }
}
      

2.使用同步函數

在方法上修飾synchronized 稱為同步函數

public synchronized void sale() {
    if (trainCount > 0) {
        try {
            Thread.sleep(40);
        } catch (Exception e) {
        }
        System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
        trainCount--;
    }
}
      

3.靜态同步函數

方法上加上static關鍵字,使用synchronized 關鍵字修飾 為靜态同步函數

靜态的同步函數使用的鎖是 該函數所屬位元組碼檔案對象

問:同步代碼塊與同步函數差別?

答:

同步代碼使用自定鎖(明鎖)

同步函數使用this鎖

問:同步函數與靜态同步函數差別?

注意:有些面試會這樣問:例如現在一個靜态方法和一個非靜态靜态怎麼實作同步?

答:

同步函數使用this鎖

靜态同步函數使用位元組碼檔案,也就是類.class

問:什麼是多線程死鎖?

答:

繼續閱讀