日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

多線程編程(下):線程同步&通信

作者:Ombré_mi 更新時間: 2022-05-11 編程語言

目錄

一. 線程安全問題

二. 你的多線程協調嗎 —— synchronized

1. 同步代碼塊

2. 同步方法

三. 什么是易變數據 —— volatile

四. 要協調必須等待 —— wait方法

五. 你的線程協調得到通知了嗎 —— notify 或 notifyAll


? ? ? ?在多線程的程序中,有多個線程并發運行,這多個并發執行的線程往往不是孤立的,它們之間可能會共享資源,也可能要相互合作完成某一項任務,如何使這多個并發執行的線程在執行的過程中不產生沖突,使多線程編程必須解決的問題。否則,可能導致線程運行的結果不正確,甚至造成死鎖問題。

一. 線程安全問題

? ? ? ?在進行多線程的程序設計時,有時需要實現多個線程共享同一段代碼,從而實現共享同一個私有成員或類的靜態成員的目的。這時,由于線程和線程之間爭搶CPU資源,線程無序地訪問這些共享資源,最終可能導致無法得到正確的結果。這些問題通常稱為線程安全問題

? ? ? ?以火車站售票系統為例,在代碼中判斷當前票數是否大于0,如果大于0則執行將該票出售給乘客的功能,但當兩個線程同時訪問這段代碼時(假如這時只剩下一張票),第一個線程將票售出,與此同時第二個線程也已經執行完成判斷是否有票的操作,并得出票數大于0的結論,于是它也執行售出操作,這樣就會產生負數。所以,在編寫多線程程序時,應該考慮到線程安全問題。實質上線程安全問題來源于兩個線程同時存取單一對象的數據。

? ? ? ?例如,在項目中創建ThreadSafeTest類,該類實現了Runnable接口,在未考慮到線程安全問題的基礎上,模擬火車站售票系統的功能的代碼如下:

public class ThreadSafeTest1 implements Runnable{
    int num = 10;

    @Override
    public void run() {
        while(true){
            if(num > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->票數" + num--);
            }
        }
    }

    public static void main(String[] args) {
        ThreadSafeTest1 t = new ThreadSafeTest1();
        Thread t1 = new Thread("線程一");
        Thread t2 = new Thread("線程二");
        Thread t3 = new Thread("線程三");
        Thread t4 = new Thread("線程四");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
-----------------------------------------------------------------------------------------
結果:
線程四-->票數10
線程三-->票數9
線程二-->票數8
線程一-->票數7
線程四-->票數6
線程三-->票數5
線程二-->票數4
線程一-->票數3
線程四-->票數2
線程三-->票數1
線程二-->票數-1
線程一-->票數0
線程四-->票數-2

? ? ? ?從這個結果可以看出,最后打印出的剩下的票數為負值,這樣就出現了問題。這是由于同時創建了4個線程,這4個線程執行run()方法,在num變量為1時,線程一、線程二、線程三、線程四都對num變量有存儲功能,當線程一執行run()方法時,還沒有來得及做遞減操作,就指定它調用sleep()方法進入就緒狀態,這時線程二、線程三和線程四也都進入了run()方法,發現num變量依然大于0,但此時線程一休眠時間已到,將num變量值遞減,同時線程二、線程三、線程四也都對num變量進行遞減操作,從而產生了負值。

二. 你的多線程協調嗎 —— synchronized

1. 同步代碼塊

? ? ? ?那么,該如何解決資源共享的問題呢?所有解決多線程資源沖突問題的方法基本上都是采用給定時間只允許一個線程訪問共享資源的方法,這時就需要給共享資源上一道鎖。這就好比一個人上洗手間時,他進入洗手間后會將門鎖上,出來時再將鎖打開,然后其他人才可以進入。

? ? ? ?Java中提供了同步機制,可以有效地防止資源沖突。同步機制使用synchronized關鍵字,使用該關鍵字包含的代碼塊稱為同步塊,也稱為臨界區,語法如下:

synchronized(synObject){
    //關鍵代碼
}

? ? ? ?通常將共享資源的操作放置在synchronized定義的區域內,這樣當其他線程獲取到這個鎖時,就必須等待鎖被釋放后才可以進入該區域。Object為任意一個對象,每個對象都存在一個標志位,并具有兩個值,分別為0和1。一個線程運行到同步塊時首先檢查該對象的標志位,如果為0狀態,表明此同步塊內存在其他線程,這時當期線程處于就緒狀態,直到處于同步塊中的線程執行完同步塊中的代碼后,這時該對象的標識位設置為1,當期線程才能開始執行同步塊中的代碼,并將Object對象的標識位設置為0,以防止其他線程執行同步塊中的代碼。

創建SynchronizedTest類,修改之前線程不安全的火車售票系統,把對num操作的代碼設置在同步塊中。

public class ThreadSafeTest1 implements Runnable{
    int num = 10;

    @Override
    public void run() {
        while(true){
            synchronized (this){
                if(num > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-->票數" + num--);
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadSafeTest1 t = new ThreadSafeTest1();
        Thread t1 = new Thread("線程一");
        Thread t2 = new Thread("線程二");
        Thread t3 = new Thread("線程三");
        Thread t4 = new Thread("線程四");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
-----------------------------------------------------------------------------------------
結果:
線程一-->票數10
線程一-->票數9
線程一-->票數8
線程一-->票數7
線程一-->票數6
線程一-->票數5
線程一-->票數4
線程一-->票數3
線程一-->票數2
線程一-->票數1

從這個結果可以看出,打印到最后票數沒有出現負數,這是因為將共享資源放置在了同步塊中,不管程序如何運行都不會出現負數。

2. 同步方法

? ? ? ?同步方法就是在方法前面用synchronized關鍵字修飾的方法,其語法如下:

public synchronized void Main();

? ? ? ? 當某個對象調用了同步方法時,該對象上的其他同步方法必須等待該同步方法執行完畢后才能被執行。必須將每個能訪問共享資源的方法修飾為synchronized,否則就會出錯。

修改上面的代碼,將共享資源操作放置在一個同步方法中,代碼如下:

int num = 10;
public synchronized void doit(){
    if(num > 0){
        try {
             Thread.sleep(100);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "-->票數" + num--);
    }
}

public void run(){
    while(true){
        doit();
    }
}

三. 什么是易變數據 —— volatile

volatile 的定義:

? ? ? ?java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致的更新,線程應該確保通過排他鎖單獨獲得這個變量。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個字段被聲明成volatile,java線程內存模型確保所有線程看到這個變量的值是一致的.

Volatile是輕量級的synchronized,輕量級體現在一下幾個方面:

  • volatile?變量所需的編碼較少
  • 運行時開銷也較少
  • 不會引起線程上下文的切換和調度(線程上下文即線程的運行環境)

例1:

private volatile int num = 0;
private volatile String sum = nulll;

例2:假設多線程共同訪問一個數組,即許多線程對這個數組進行排序,而同時其他線程打印這個數組的最小值和最大值。

...
static final int Size = 100;
static volatile int num[] = new int[Size];
static volatile int first = 0;
static volatile int last = 0;
static volatile boolean ready = false;
...

四. 要協調必須等待 —— wait方法

? ? ? ?wait() 方法和 notify() 或 notifyAll() 應當在 synchronized 的成程序塊或者方法中配合使用,使多線程在共享資源和數據時得到進一步的保障。wait() 拋出檢查性異常 InterruptedException。在一個?synchronized 的代碼中調用 wait() 必須提供這個異常處理機制,例如:

try {
    if (!ready){
        wait();
        ...
    }
}
...
? ? ? ?導致其他試圖進入這個?synchronized 代碼中的線程放棄監視器和鎖定,保證只有當前線程執行這段協調代碼。實際上,放棄鎖定的其他所以線程都進入等待狀態,直到某個在監視器中運行的線程調用?notify() 或者?notifyAll() ,例如:
if(ready){
    notifyAll();
...

五. 你的線程協調得到通知了嗎 —— notify 或 notifyAll

? ? ? ?notify() 和?notifyAll() 必須和 wait() 配合使用。notify() 只是喚醒一個正在等待的線程。如果代碼中只有一個線程處于等待狀態,調用?notify() 不存在問題。由于系統調度器處理線程調度安排的不透明性,喚醒哪個線程是不確定的。所以?notifyAll() 被經常使用,以增強等待線程被通知的可靠性。

?

原文鏈接:https://blog.csdn.net/Lucky_mzc/article/details/124566148

欄目分類
最近更新