JUC之Callable介面回顧和JUC輔助類

xbhog發表於2022-01-07

Callable介面和JUC輔助類

Callable介面:

回顧:

建立執行緒的四種方式:

  1. 繼承Thread
  2. 實現runnable介面
  3. 實現callable介面
  4. 使用執行緒池

之前的文章:多執行緒程式設計1-定義理解與三種實現方式

Runnable和Callable介面的差異:

  1. Runnable無返回值,Callable有返回值
  2. Runnable不拋異常,Callable拋異常
  3. 實現名稱不同,Runnable是run方法,Callable是call方法
class MyThread1 implements Runnable{

    @Override
    public void run() {
        
    }
}

class MyThread2 implements Callable{

    @Override
    public Integer call() throws Exception {
        return 200;
    }
}

Runnable 介面實現類FutureTask

FutureTask構造可以傳遞callable

這是類的繼承結構:

別名:可取消的非同步,簡單的理解是當主執行緒中存在耗時高的任務時,可以單開一個子執行緒處理,主執行緒處理耗時少的任務,最終匯合在一起。

需要注意的是,使用FutureTask當得到第一次結果後,第二次獲取時直接返回結果,也可以說所有的任務只彙總一次。

JUC輔助類:

CountDownLatch(減少計數)

定義:一個同步輔助類,在完成一組正在其他執行緒中執行的操作之前,它允許一個或多個執行緒一直等待。用給定的計數 初始化 CountDownLatch。由於呼叫了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的執行緒,await 的所有後續呼叫都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier

CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口:在通過呼叫 countDown() 的執行緒開啟入口前,所有呼叫 await 的執行緒都一直在入口處等待。用 N 初始化的 CountDownLatch 可以使一個執行緒在 N 個執行緒完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待

/**
 * 問題,當六個人走出教室,則班長鎖門
 */
public class CountDownLatch07 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(6);
        for(int i = 1; i <= 6; i++){
            new Thread(() ->{
                System.out.println(Thread.currentThread().getName()+" 號同學走出教室");
                latch.countDown();
            },String.valueOf(i)).start();
        }
        latch.await();
        System.out.println(Thread.currentThread().getName()+"班長鎖門");
    }
}

其使用的方法,CountDownLatch latch = new CountDownLatch(6)、latch.countDown()、latch.await();

CyclicBarrier(迴圈柵欄)也可以實現CountDownLatch效果,CyclicBarrier在所有執行緒執行完畢之後是可以重用的。

CyclicBarrier(迴圈柵欄)

原始碼定義:

一種同步輔助工具,它允許一組執行緒全部等待彼此到達公共屏障點。 CyclicBarriers 在涉及固定大小的執行緒組的程式中很有用,這些執行緒必須偶爾相互等待。 屏障被稱為迴圈的,因為它可以在等待執行緒被釋放後重新使用。

簡單的理解,當達到設定的要求後,執行特定的內容,相當於監聽器;

例項程式碼:

public class JucUtils {
    private static final Integer NUMBER = 7;
    public static void main(String[] args) {
        //設定資源要求,達到後所需要執行的任務
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
            System.out.println("資源達到要求!");
        });
		//對設定的目標前進
        for (int i = 1; i <= 7; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    System.out.println("資源正在收集:"+ Thread.currentThread().getName());
                    //等待,當等待的執行緒數量到達設定的值,呼叫執行任務並釋放這些執行緒。
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

感興趣的可以看看原始碼:

-------------------------CyclicBarrier--------------------
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties; 
    this.count = parties;  //設定臨界值
    this.barrierCommand = barrierAction;
}
----------------------------await-----------------------
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
//------------------dowait部分程式碼-------------------
final Generation g = generation;

if (g.broken)
    throw new BrokenBarrierException();

if (Thread.interrupted()) {
    breakBarrier();
    throw new InterruptedException();
}

int index = --count;  //每次等待,所需資源-1;
if (index == 0) {  // tripped
    boolean ranAction = false;
    try {
        final Runnable command = barrierCommand;
        if (command != null)
            command.run();
        ranAction = true;
        nextGeneration();
        return 0;
    } finally {
        if (!ranAction)
            breakBarrier();
    }
}

//------nextGeneration----------
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();  //喚醒執行緒
    // set up next generation
    count = parties;
    generation = new Generation();
}

Semaphore(訊號燈)

如果對作業系統有所瞭解的話,該工具類就是訊號量+pv操作的集合,對訊號量的操作只有三種,初始化、p操作、v操作,其中訊號量就是Semaphore初始化的(某種資源的數量),p操作對應的是semaphore.acquire(),訊號量--,v操作對應的semaphore.release(),訊號量++,當Semaphore初始化唯1時,則為互斥資源。

package com.JUC;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 類比作業系統的中訊號量PV操作
 */
public class Semaphore07 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for(int i = 1; i <= 6; i++){
            new Thread(()->{
                try {
                    semaphore.acquire();  //加鎖
                    System.out.println(Thread.currentThread().getName()+" 搶到了車位");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));//隨機時間停車
                    System.out.println(Thread.currentThread().getName()+"-------離開了車位");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally{
                    semaphore.release(); //解鎖
                }
            },String.valueOf(i)).start();
        }
    }
}

相關文章