學習筆記五:線程間的協做與通訊

常見的線程之間通訊方式有以下幾種:java

一、wait和notify/notifyAll併發

二、await和signal/signalAllide

三、sleep/yield/join工具

四、同步屏障CyclicBarrier學習

五、CountDownLatch 閉鎖spa

六、Semaphore 信號量.net

注意:四、五、6參見後續章節線程

一、wait和notify/notifyAll 

等待/通知機制,是指一個線程A調用了對象O的wait方法進入等待狀態,而另一個線程B調用了對象O的notify()或者notifyAll()方法,線程A收到通知後從對象O的wait()方法返回,進而執行後續操做。上述兩個線程經過對象O完成交互,而對象上的wait()和notify()/notifyAll()的關係就如同開關信號同樣,用來完成等待方和通知方之間的交互工做。code

建立兩個線程WaitThread和NotifyThread,前者檢查flag值是否爲false,若是符合要求,進行後續操做,不然在lock上等待,後者在睡眠了一段時間值對lock進行通知,示例代碼以下:orm

package com.black.example.helloworld.thread;

import ch.qos.logback.core.util.TimeUtil;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 等待/通知 超時設置模式
 */
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 WaitDemo(),"WaitThread");
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);
        Thread notifyThread =new Thread(new NotifyDemo(),"NotifyThread");
        notifyThread.start();

    }

    static class WaitDemo implements Runnable{
        @Override
        public void run() {
            //加鎖,擁有lock的monitor
            synchronized (lock){
                //條件不知足時,繼續wait,同時釋放lock的鎖
                while (flag){
                    try {
                        System.out.println(Thread.currentThread()+"flag is true, wait @ "+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //條件知足時,完成工做
                System.out.println(Thread.currentThread() + " flag is false,running @ "+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }
    }

    static class NotifyDemo implements Runnable{
        @Override
        public void run() {
            try {
                //加鎖,擁有lock對象的Monitor
                synchronized (lock){
                    //獲取lock的鎖,而後進行通知,通知時不會釋放lock的鎖
                    //直到當前線程釋放了lock後,WaitThread才能從wait方法中返回
                    System.out.println(Thread.currentThread()+" hold lock, notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    lock.notifyAll();
                    flag = false;
                    TimeUnit.SECONDS.sleep(5);
                }

                //再次加鎖
                synchronized (lock){
                    System.out.println(Thread.currentThread()+" hold lock again, notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    TimeUnit.SECONDS.sleep(5);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

}

運行結果以下:

二、await和signal/signalAll 

Condition定義了等待/通知兩種類型的方法,當前線程調用這些方法時,須要提早獲取Condition對象關聯的鎖。Condition對象是由Lock對象(調用Lock對象的newCondition()方法)建立出來的,換句話說,Condition是依賴Lock對象的。

通常都會講Condition對象做爲成員變量。當調用await()方法後,當前線程會是否鎖並進入等待,並且其餘線程調用signal()方法,通知當前線程後,當前線程才從await()方法返回,而且在返回前已經獲取到了鎖。

經常使用方法及描述,見下表:

await()  等待  與 singnal()通知的使用,示例以下:

package com.black.example.mutileThread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Condition 配合Lock  實現線程的等待 與通知
 * Created by liuzp on 2018/7/24.
 */
public class ConditionDemo {
    public static Lock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                lock.lock();//請求鎖
                Thread.currentThread().setName("await-thread-lzp");
                try {
                    System.out.println(Thread.currentThread().getName() + "==》進入等待狀態,直到被通知");
                    condition.await();//設置當前線程進入等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//釋放鎖
                }
                System.out.println(Thread.currentThread().getName() + "==》繼續執行");
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                lock.lock();//請求鎖
                Thread.currentThread().setName("signal-thread-lzp");
                try {
                    System.out.println(Thread.currentThread().getName() + "=》進入喚醒一個Condition上的線程");
                    Thread.sleep(2000);//休息2秒
                    condition.signal();//隨機喚醒等待隊列中的一個線程
                    System.out.println(Thread.currentThread().getName() + "休息結束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//釋放鎖
                }
            }
        }.start();
    }
}

運行結果:

三、sleep/yield/join

  1. 對於sleep()方法應該很熟悉了,讓當前線程睡眠一段時間。期間不會釋聽任何持有的鎖
  2. yield()方法其做用主要是讓當前線程從運行狀態轉變爲就緒狀態,由線程調度從新選擇就緒狀態的線程分配CPU資源。至於最終會選取哪一個線程分配CPU資源就由調度策略來決定了,有可能仍是該線程,有可能換爲其它線程。
  3. join方法,做用是暫停當前線程,等待被調用線程指向結束以後再繼續執行。

使用join的時候須要注意:
  一、調用join的時候,當前線程不會釋放掉鎖,若是調用線程也須要該鎖則就會致使死鎖!
  二、join方法不會啓動調用線程,因此,在調用join以前,該調用線程必須已經start啓動,不然不會達到想要的效果。
join的底層實際是就是使用了一個自旋等待機制,判斷調用線程是否死亡,若是沒有則一直讓當前線程wait。能夠看一下底層實現源碼:

public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {//若是調用者依舊沒有結束,讓當前線程進行等待
                wait(0);//注意這裏的wait是等待的當前線程,而不是調用者線程
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);//指定等待的時間
                now = System.currentTimeMillis() - base;
            }
        }
    }

 

上一篇:學習筆記四:初識線程

下一篇:學習筆記六:線程間的協做與通訊之併發工具類

相關文章
相關標籤/搜索