【Java多線程】多線程之間是如何實現通信的?

你和你的朋友共同完成一個任務,你們隨時交流對方的工作進度,因爲有些任務是要對等待對方先完成再做,這個過程就是線程通信。


Table of Contents

synchronized實現通信

重入鎖(ReentrantLock)實現通信

線程之間的通訊工具類

CountDownLatch(閉鎖)

CyclicBarrier(柵欄)

Semaphore(信號量)


synchronized實現通信

進入等待(是Object類的方法):

  1. void wait();
  2. void wait(long timeoutMillis)
  3. void wait(long timeoutMillis, int nanos)
  4. wait方法執行後,會釋放監視器,會釋放鎖!

喚醒(是Object類的方法):

  1. void notify();  隨機喚醒一個正在wait的方法;
  2. void notifyAll();  喚醒所有正在wait的方法;

第一個同步程序:用三個線程模擬三個人報數。

/**
 * 功能:三個線程輪流報數
 *
 * @author KYLE
 *
 */
public class Main {

    public static void main(String[] args) {
        CountOff co = new CountOff();
        Thread t1 = new Thread(co);
        Thread t2 = new Thread(co);
        Thread t3 = new Thread(co);

        t1.setName("線程1");
        t2.setName("線程2");
        t3.setName("線程3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class CountOff implements Runnable {
    private volatile int initNum = 1;
    private final int endNum = 10;

    @Override
    public void run() {
        synchronized (this) {
            while(initNum <= 10) {
                try {
                    System.out.println(Thread.currentThread().getName() + "報數:" + initNum++);
                    this.notifyAll(); //喚醒其他線程開始執行
                    this.wait(); //進入等待!
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.notifyAll();
        }
    }
}

重入鎖(ReentrantLock)實現通信

進入等待(juc.locks.Condition接口):

  1. void await()   將線程放在這個條件的等待集中。
  2. boolean await(long time, TimeUnit unit)   使當前線程等待直到發出信號或中斷,或指定的等待時間過去。
  3. long awaitNanos(long nanosTimeout)   使當前線程等待直到發出信號或中斷,或指定的等待時間過去。
  4. void awaitUninterruptibly()   使當前線程等待直到發出信號。
  5. boolean awaitUntil(Date deadline)   使當前線程等待直到發出信號或中斷,或者指定的最後期限過去。

 

喚醒:

  1. void signal()   隨機喚醒一個等待集中的線程,解除其阻塞狀態。
  2. void signalAll()   喚醒所有等待線程,解除阻塞狀態。 

第一個重入鎖實現的線程間通信:

程序功能:A線程通知B線程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo {
	
	public static void main(String[] args) {
		ReentrantLockCondition rc = new ReentrantLockCondition();
		Thread t1 = new Thread(rc,"線程1");
		
		t1.start();
		
		//通知rc對象的t1線程繼續執行!!!
		//如果沒有以下三段代碼,則在Console只會顯示:線程1開始執行。。。
		rc.lock.lock();
		rc.cond.signal(); //通知rc對象的t1線程:可以繼續執行了!
		rc.lock.unlock();
	}
}

class ReentrantLockCondition implements Runnable {
	public ReentrantLock lock = new ReentrantLock();
	public Condition cond = lock.newCondition(); //newCondition方法是juc.locks.Lock接口中的方法
	
	@Override
	public void run() {
		try {
			lock.lock();
			System.out.println(Thread.currentThread().getName()+"開始執行。。。");
			cond.await(); //使得本線程進入等待,並且能夠釋放當前鎖
			System.out.println(Thread.currentThread().getName()+"線程繼續執行。。。");
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

輸出:
線程1開始執行。。。
線程1線程繼續執行。。。

線程之間的通訊工具類

爲了能夠更好控制線程之間的通信,Java爲我們提供了一些工具類:

  1. CountDownLatch(閉鎖)
  2. CyclicBarrier(柵欄)
  3. Semaphore(信號量)

CountDownLatch(閉鎖)

CountDownLatch的功能:CountDownLatch是一個同步的輔助類,允許一個或多個線程一直等待,直到其它線程完成它們的操作。

特性:

  1. 來自juc.CountDownLatch類
  2. 它常用的API其實就兩個:await()和countDown()

理解:

  1. count初始化CountDownLatch,然後需要等待的線程調用await方法。await方法會一直受阻塞直到count=0。而其它線程完成自己的操作後,調用countDown()使計數器count減1。當count減到0時,所有在等待的線程均會被釋放
  2. 說白了就是通過count變量來控制等待,如果count值爲0了(其他線程的任務都完成了),那就可以繼續執行。
  3. 實例:你是一個實習生,其他的員工還沒下班,你也不好意思走,等其他的員工都走光了,你再走。
第一個CountDownLatch程序
import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) {
        YourName aa = new YourName();
        aa.GoOffWork(); //下班了
    }
}

//你是一個實習生,在XxOo公司上班,其他的員工還沒下班,你也不好意思走,等其他的員工都走光了,你再走。(改編自:Java3y)
class YourName {
    private int num; //公司人數
    private final CountDownLatch cd;

    public YourName() {
        Random rand = new Random();
        this.num = rand.nextInt(10); //隨機獲取一個公司人數
        cd = new CountDownLatch(num); //你必須等待公司全部人走了你才能走
    }

    //下班了
    public void GoOffWork() {
        System.out.println("現在6點下班了。。。");

        //啓動你自己的下班線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //這裏調用的是await()而不是wait()方法
                    cd.await(); //要等待其他人都走了(執行完畢),你才能走(執行)
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("其他員工都走了,我也可以下班了");
            }
        }).start();

        //公司其他員工的下班線程
        for(int i=0; i<num; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("員工xx下班了!");
                    cd.countDown();
                }
            }).start();
        }
    }
}

CyclicBarrier(柵欄)

理解:

  1. CyclicBarrier允許一組線程互相等待,直到到達某個點。
  2. 叫做cyclic是因爲當所有等待線程都被釋放以後,CyclicBarrier可以被重用(對比於CountDownLatch是不能重用的)
  3. 區別:CountDownLatch注重的是等待其他線程完成;CyclicBarrier注重的是:當線程到達某個狀態後,暫停下來等待其他線程,所有線程均到達以後,繼續執行。
  4. 例如:你和你的朋友們(一組線程)約定一起去旅遊,要等到所有朋友都到齊了以後(線程間相互等待),才能出發(到達某個點)。
第一個CyclicBarrier程序
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Main {
    public static void main(String[] args) {
        Meet meet = new Meet();

        Thread t0 = new Thread(meet);
        Thread t1 = new Thread(meet);
        Thread t2 = new Thread(meet);
        Thread t3 = new Thread(meet);
        Thread t4 = new Thread(meet);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class Meet implements Runnable {
    private final CyclicBarrier cb = new CyclicBarrier(5, ()->{
        System.out.println("所有朋友都到了,可以出發去旅遊了!");
    }); //你有5個朋友相約一起去旅遊

    @Override
    public void run() {
        try {
            synchronized (this) {
                speedTime();
            }
            cb.await(); //等待所有朋友到齊
        } catch(InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    //每個朋友需要到達聚會點的所花時間
    public void speedTime() {
        try {
            Thread.sleep(2000); //模擬這個朋友需要2s才能到達聚會點
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("朋友(線程):"+Thread.currentThread().getName()+"到達了!");
    }
}

Semaphore(信號量)

背景:

  1. 無論是內部鎖synchronized還是重入鎖ReentrantLock,一次都只允許一個線程訪問一個資源;
  2. 那麼,有些資源必須允許多個線程訪問同時訪問,應該怎麼實現呢?
  3. 例如,某個小店(資源)一次只能允許5個顧客(多個線程)同時挑選購買,超過5個就需要排隊等待了。

理解:

  1. Semaphore(信號量)實際上就是可以指定多個線程同時訪問某一資源的一種解決方案。

構造方法:

  1. Semaphore(int permits) 使用給定數量的許可和非公平方式設置創建 Semaphore 。
  2. Semaphore(int permits, boolean fair) 使用給定數量的許可和公平的方式設置創建 Semaphore 。

核心方法:

  1. void acquire()  從此信號量上獲取一個許可;如果沒有則一直阻塞,直到有一個可用;或者線程爲interrupted
  2. void acquire(int permits)  從此信號量獲取給定數量的許可;阻塞直到所有可用;或者線程爲 interrupted
  3. void acquireUninterruptibly() 從此信號量獲取許可,如果沒有一直阻塞,直到有一個可用

 

  1. boolean tryAcquire(int permits)  只有在調用時所有許可都可用時,才從此信號量獲取給定數量的許可。【嘗試獲取】
  2. boolean tryAcquire(int permits, long timeout, TimeUnit unit)  如果在給定的等待時間內所有許可都可用,且當前線程不是 interrupted ,則從此信號量獲取給定數量的許可。【時間等待獲取】
  3. boolean tryAcquire(long timeout, TimeUnit unit)  如果在給定的等待時間內有可用的並且當前線程不是 interrupted ,則從該信號量獲取許可。【時間條件獲取】
  4. void release()  釋放許可。
第一個信號量程序
import java.util.concurrent.Semaphore;

public class Main {
    public static void main(String[] args) {
        SemaphoreExample se = new SemaphoreExample();
        //準備6個線程
        Thread t0 = new Thread(se);
        Thread t1 = new Thread(se);
        Thread t2 = new Thread(se);
        Thread t3 = new Thread(se);
        Thread t4 = new Thread(se);
        Thread t5 = new Thread(se);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

class SemaphoreExample implements Runnable {
    private final Semaphore sema = new Semaphore(5); //某一資源同一時刻只能允許5個線程訪問

    @Override
    public void run() {
        try {
            sema.acquire(); //申請對該資源的訪問,但構造方法規定該資源一次只能允許5個線程同時訪問
            System.out.println("線程"+Thread.currentThread().getName()+"正在訪問該資源!");
            Thread.sleep(3000); //一旦申請成功,立即休眠3s,模擬該線程執行任務
        } catch(InterruptedException e) {
            e.printStackTrace();
        } finally {
            sema.release();
        }
    }
}

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章