多執行緒併發執行及解決方法

小小的香辛料發表於2019-02-02

用一個案例來說明:假如我們要實現一個售票的小功能,用3個執行緒售出共2000張票。

初始模型為:

package com.test7;

public class synchronizedTest {
    public static void main(String [] args){
        TicketWindow tw = new TicketWindow();
        Thread t1 = new Thread(tw);
        Thread t2 = new Thread(tw);
        Thread t3 = new Thread(tw);
        t1.start();
        t2.start();
        t3.start();
    }

}

class TicketWindow implements Runnable{
    int nums = 2000;

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(nums>0){
                System.out.println(Thread.currentThread().getName()+"當前正在售出弟"+nums+"張票");
                nums--;
            }
            else{
                break;
            }
        }
    }
}

 程式碼執行結果為:

但是我們發現這3個執行緒會出現同時售出一張票的情況,這是怎麼回事呢?以售出第1766張票為例,因為這3個執行緒是多執行緒併發執行的,當其中一個執行緒Thread-2訪問第1766張票時,進入if語句,執行完

System.out.println(Thread.currentThread().getName()+"當前正在售出弟"+nums+"張票");

這條語句而還未執行nums--時,另一個執行緒Thread-1也開始訪問第1766張票,進入了if語句,開始執行

System.out.println(Thread.currentThread().getName()+"當前正在售出弟"+nums+"張票");

此時便會同時售出兩張1766張票。

我們知道,這種執行緒是極其危險的,那麼怎麼解決,讓執行緒變得安全呢?

其實很簡單,只需要加一個物件鎖,什麼是物件鎖呢?別急,往下看。

如果我們把上面執行緒併發執行的過程抽象成上廁所的話,就一個馬桶,很多個人在外面排隊,如果兩個或者多個人程同時上一個廁所,就很容易出現問題,這種執行緒是不安全的。要解決問題只需要在門上安一把鎖,必須等上一個人解決了下一個人才能進入,要解決的一個一個來。(擴充套件一下,會不會有一直解決不了的情況呢?會,比如當一個執行緒a在A廁所中執行執行緒,執行緒b等a解決完了才能進入A廁所,而a執行過程中要呼叫B廁所,而B廁所正好有人在裡面了,這個人正好是B。這樣的話,執行緒A永遠結束不了,就出現了死鎖問題)

我們解決上述問題的方法就是加個物件鎖,物件鎖有0和1兩種狀態,預設情況為1,代表廁所裡沒人,執行緒可以進入。一旦執行緒進入,她就會變成0---有人的狀態,其他執行緒不能進入,不能進入的執行緒會進入執行緒等待池中(blocked阻塞狀態)。任何物件都可以充當物件鎖,一般用this充當物件鎖。

程式碼是

synchronized (Object) {要執行緒同步的程式碼塊}

最終修改程式碼為:

package com.test7;

public class synchronizedTest {
    public static void main(String [] args){
        TicketWindow tw = new TicketWindow();
        Thread t1 = new Thread(tw);
        Thread t2 = new Thread(tw);
        Thread t3 = new Thread(tw);
        t1.start();
        t2.start();
        t3.start();
    }

}

class TicketWindow implements Runnable{
    int nums = 2000;

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this) {
                if (nums > 0) {
                    System.out.println(Thread.currentThread().getName() + "當前正在售出弟" + nums + "張票");
                    nums--;
                } else {
                    break;
                }
            }
        }
    }
}

執行結果為:

相關文章