天天看點

線程間通信-java并發程式設計的藝術volatile synchroized

volatile synchroized

java支援多個多個線程同時通路一個對象或者對象的成員變量,由于每個線程都可以擁有這個變量的拷貝,這樣的目的在于加速程式的執行。是以,一個線程看到的變量的并不一定就是最新的。

volatile可以修飾字段(成員變量),就是告知程式任何對該變量的通路都需要從共享記憶體中擷取,對他的改變必須同步重新整理到共享記憶體(主記憶體),保證所有線程對這個變量的可見性。

synchorized可以修飾方法或者修飾代碼塊,主要就是確定多個線程在同一時刻,隻能有一個線程處于方法或者代碼塊中,保證線程對變量通路的可見性課排他性。

public class Synchorized {
    public static void main(String[] args) {
        synchronized (Synchorized.class) {

        }
        m();
    }

    public static synchronized void m() {

    }

}

```運作javap  -v指令會發現

           

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=3, args_size=1

0: ldc #2 // class ja

va并發程式設計的藝術/one/Synchorized

2: dup

3: astore_1

4: monitorenter

5: aload_1

6: monitorexit

7: goto 15

10: astore_2

11: aload_1

12: monitorexit

13: aload_2

14: athrow

15: invokestatic #3 // Method m

😦)V

18: return

public static synchronized void m();

descriptor: ()V

flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED

Code:

stack=0, locals=0, args_size=0

0: return

LineNumberTable:

line 13: 0

上面的class資訊中,

同步塊的實作使用了monitorenter和monitorexit指令。

而同步方法是依靠方法修飾符上的ACC_SYNCHRONIZED來完成的。

上面兩個方式的的本質都是:對一個對象的螢幕(monitor)的擷取。則這個過程是排他的。同一時刻,隻能有一個線程擷取到這個由synchorized所保護的對象的螢幕。

任意一個對象都擁有自己的螢幕,當這個對象由同步塊或者同步方法調用時,必須先擷取該對象的螢幕才能進入。沒有擷取到螢幕的線程就會阻塞到同步塊或者同步方法的入口處。進入BLOCKED狀态。

線程間通信-java并發程式設計的藝術volatile synchroized

上圖中,如果Object的前驅(擷取到鎖的線程)釋放了鎖。這個釋放操作就會喚醒阻塞在同步隊列彙總的線程,使其重新嘗試對螢幕的擷取。

線程間通信-java并發程式設計的藝術volatile synchroized
public class WaitNotify {
    static boolean flag = true;
    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread waitThread = new Thread(new Wait(), "wait thread");
waitThread.start();
        TimeUnit.SECONDS.sleep(1);
        Thread notifyThread = new Thread(new Notify(),"notify thread");
        notifyThread.start();
    }

    static class Wait implements Runnable {
        @Override
        public void run() {
            //加鎖,擁有lock的monitor
            synchronized (lock) {
                while (flag) {
                    try {
                        System.out.println(Thread.currentThread() + "flag is true,wait___" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                        lock.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                //條件滿足時,完成工作

                System.out.println(Thread.currentThread() + "flag is false,running___" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }
    }

    static class Notify implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                //擷取鎖,然後通知,通知的時不會釋放鎖
                //直到目前線程釋放了鎖,waitThread才能從wait中傳回
                System.out.println(Thread.currentThread() + "hold  lock。notify___" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                lock.notifyAll();
                flag = false;
                SleepUtils.second(5);
            }
            //再次加鎖
            synchronized (lock) {
                System.out.println(Thread.currentThread() + "hold  lock again。notify___" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                SleepUtils.second(5);

            }
        }
    }


}

           

輸出:

Thread[wait thread,5,main]flag is true,wait___00:42:28
Thread[notify thread,5,main]hold  lock。notify___00:42:29
Thread[notify thread,5,main]hold  lock again。notify___00:42:34
Thread[wait thread,5,main]flag is false,running___00:42:39
           
  1. 調用wait()、notify()、notifyAll()時,需要先對調用對象加鎖
  2. 調用wait()後,線程狀态由RUNNING 變為WAITING,并且将目前線程防止到對象的等待隊列。
  3. notify()或者notifyAll()調用後,等待線程依舊不會從wait()傳回,需要調用notify()或者notifyAll()的線程釋放鎖後,等待線程才有機會從wait()傳回。
  4. notify()方法将等待隊列總的一個等待線程從等待隊列移到同步隊列中,而notifyAll()方法則是将的等待隊列中所有線程全部移到同步隊列,狀态由WAITING變為BLOCKED
  5. 從wait()方法傳回的前提是獲得了調用對象的鎖。
  6. 線程間通信-java并發程式設計的藝術volatile synchroized
    上圖中,waitThread先擷取了對象的鎖,然後調用對象的wait方法,進而放棄了鎖并進入到等待隊列,進入等待狀态。由于waitThead釋放了鎖,NotifyThread随後便擷取了對象的鎖,并調用了對象的notify()方法,将waitThread 從等待隊列中移到同步隊列中,此時,waitThread的狀态變為阻塞狀态,NotifyThread釋放了鎖之後,waitThread再次擷取鎖并從wait()方法中傳回。

等待/通知的範式

等待方(消費者):

  1. 擷取對象的鎖
  2. 如果條件不滿足,調用對象的wait()方法,被通知後仍要檢查條件
  3. 條件滿足,執行代碼
synchronized (對象){
  while(條件不滿足)

    {
        對象.wait();
    }

    dothings();
}
           

通知方(生産者)

  1. 擷取對象的鎖
  2. 改變條件
  3. 通知所有等待在對象上的線程
synchronized (對象){
 改變條件;
 對象.notifyAll();
}
           

Thread.join()的使用

thread.join():目前線程A等待線程終止後才從thread.join()傳回。

join(long millis):在特定時間裡沒有終止,就從逾時方法中傳回

public class Join {


    public static void main(String[] args) {
        Thread previous = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
              //每個線程擁有前一個線程的引用,需要等前一個線程終止,才能從等待中傳回
            Thread thread = new Thread(new Demino(previous), String.valueOf(i));
            thread.start();
            previous = thread;
        }
    }


    static class Demino implements Runnable {
        private Thread thread;

        public Demino(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "終止了");
        }
    }
}

           
0終止了
1終止了
2終止了
3終止了
4終止了
5終止了
6終止了
7終止了
8終止了
9終止了

           

可以看出:

每個線程的終止的前提就是前驅線程的終止,每個線程等待前驅線程終止後,才從join方法傳回。

當線程終止時,會調用線程自身的notifyAll()方法,會通知所有等待在該線程對象上的線程。

繼續閱讀