java多線程進階學習2

java多線程進階學習2

前言

歷史學習回顧:

  • CAS
  • 跳錶結構
  • copyOnWirte機制
  • Excutor框架

本章學習知識點

  • lock框架

  • FutureTask

  • 從AQS學習模板方法設計模式使用

  • fork/join框架

  • 線程異常的處理

Lock框架

位於java.util.concurrent.locks下的類。借用下網上的一張圖片來說明Lock框架中各個類的關係

img

Lock:接口,提供了下面lock的基本行爲方法

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;  // 可以響應中斷
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;  // 可以響應中斷
    void unlock();
    Condition newCondition();
}

Condition :接口類

public interface Condition {
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    void signal();
    void signalAll();
}

ReentrantLock

Lock的實現類。可重入鎖。主要利用CAS+AQS隊列來實現。它支持公平鎖和非公平鎖

abstract static class Sync extends AbstractQueuedSynchronizer {...}
static final class NonfairSync extends Sync {...}
static final class FairSync extends Sync {...}

public ReentrantLock() {
    sync = new NonfairSync();
}

 public ReentrantLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
 }

關鍵代碼:

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
       * Performs lock.  Try immediate barge, backing up to normal
       * acquire on failure.
       */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
      * Fair version of tryAcquire.  Don't grant access unless
      * recursive call or no waiters or is first.
      */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

所謂非公平其實指如果佔用鎖的線程剛釋放鎖,state置爲0,而排隊等待鎖的線程還未喚醒時,新來的線程就直接搶佔了該鎖,那麼就“插隊”了。

ReadWriteLock

public interface ReadWriteLock {
    Lock readLock();	// 獲取讀鎖
    Lock writeLock();	// 寫鎖
}

ReentrantReadWriteLock

public ReentrantReadWriteLock() {
    this(false);
}

 public ReentrantReadWriteLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
     readerLock = new ReadLock(this);
     writerLock = new WriteLock(this);
 }

Lock框架的應用基本就是前面解析其他源碼的時候用法,ReentrantLock.lock()獲取鎖,ReentrantLock.unlock()釋放鎖。

FutureTask

img

Future

參考:https://blog.csdn.net/wei_lei/article/details/74262818

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

鏈接文章分析的很詳細,總結起來就是在線程池使用的時候將callable或者runnable接口轉換成RunnableFuture。轉換的時候直接返回RunnableFuture的實現類FutureTask的實例。在線程池excute的時候執行的就是FutureTask的run方法。這個方法中將執行的結果放在一個全局result中,調用FutureTask.get()其實就是獲取result。

從AQS學習模板方法設計模式使用

先了解下模板方法模式:

定義

定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,
使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。
它是一種類行爲型模式。

結構

抽象成命令,使調用者與實現者相關分離

  • 抽象類(Abstract Class)角色:負責給出一個算法的輪廓和骨架。
    它由一個模板方法和若干個基本方法構成。這些方法的定義如下。
    ① 模板方法:定義了算法的骨架,按某種順序調用其包含的基本方法。

    ② 基本方法:是整個算法中的一個步驟,包含以下幾種類型。
    抽象方法:在抽象類中申明,由具體子類實現。
    具體方法:在抽象類中已經實現,在具體子類中可以繼承或重寫它。
    鉤子方法:在抽象類中已經實現,包括用於判斷的邏輯方法和需要子類重寫的空方法兩種。

  • 具體子類(Concrete Class):實現抽象類中所定義的抽象方法和鉤子方法,
    它們是一個頂級邏輯的一個組成步驟。

優點&缺點

  • 優點

    • 它封裝了不變部分,擴展可變部分。它把認爲是不變部分的算法封裝到父類中實現,而把可變部分算法由子類繼承實現,便於子類繼續擴展。
    • 它在父類中提取了公共的部分代碼,便於代碼複用。
    • 部分方法是由子類實現的,因此子類可以通過擴展方式增加相應的功能,符合開閉原則。
  • 缺點

    • 對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象。
    • 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度。

AQS中模板方法的使用

https://blog.csdn.net/LJJZJ/article/details/95344086

對用角色 AQS就是抽象類實現類就是對應的實現類比如說ReentrantLock。

模板方法爲

  • protected boolean isHeldExclusively() : 是否在獨佔模式下被線程佔用。只有用到condition才需要去實現它
  • protected boolean tryAcquire(int arg) : 獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false
  • protected boolean tryRelease(int arg) :獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false
  • protected int tryAcquireShared(int arg) :共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源
  • protected boolean tryReleaseShared(int arg) :共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false

AQS爲我們定義好頂級邏輯的骨架,並提取出公用的線程入隊列/出隊列,阻塞/喚醒等一系列複雜邏輯的實現,將部分簡單的可由使用者決定的操作邏輯延遲到子類中去實現即可

爲什麼上面定義的四個方法不是模板方法模式要求的抽象方法,讓子類實現呢?

這是因爲在獨佔鎖中不需要實現tryAcquireShared(),tryReleaseShared()方法,而在共享鎖中,也不需要tryAcquire(),tryRelease()方法,它們各自有自己的實現,如果定義成抽象方法,就必須實現所有,所以使用重寫。

fork/join框架

https://www.cnblogs.com/senlinyang/p/7885964.html

也稱分解/合併框架。Fork/Join框架是用來解決能夠通過分治技術將問題拆分成小任務的問題。

Fork/Join框架要完成兩件事情:

  • 任務分割:首先Fork/Join框架需要把大的任務分割成足夠小的子任務,如果子任務比較大的話還要對子任務進行繼續分割
  • 執行任務併合並結果:分割的子任務分別放到雙端隊列裏,然後幾個啓動線程分別從雙端隊列裏獲取任務執行。子任務執行完的結果都放在另外一個隊列裏,啓動一個線程從隊列裏取數據,然後合併這些數據。

在Java的Fork/Join框架中,使用兩個類完成上述操作

  • ForkJoinTask:我們要使用Fork/Join框架,首先需要創建一個ForkJoin任務。該類提供了在任務中執行fork和join的機制。通常情況下我們不需要直接集成ForkJoinTask類,只需要繼承它的子類,Fork/Join框架提供了兩個子類:

a.RecursiveAction:用於沒有返回結果的任務

b.RecursiveTask:用於有返回結果的任務

  • ForkJoinPool:ForkJoinTask需要通過ForkJoinPool來執行

任務分割出的子任務會添加到當前工作線程所維護的雙端隊列中,進入隊列的頭部。當一個工作線程的隊列裏暫時沒有任務時,它會隨機從其他工作線程的隊列的尾部獲取一個任務(工作竊取算法)。

Fork/Join框架的實現原理

ForkJoinPool由ForkJoinTask數組和ForkJoinWorkerThread數組組成,ForkJoinTask數組負責將存放程序提交給ForkJoinPool,而ForkJoinWorkerThread負責執行這些任務。

ForkJoinTask的Fork方法的實現原理:
  當我們調用ForkJoinTask的fork方法時,程序會把任務放在ForkJoinWorkerThread的pushTask的workQueue中,異步地執行這個任務,然後立即返回結果。

public final ForkJoinTask<V> fork() {
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        ForkJoinPool.common.externalPush(this);
    return this;
}

pushTask方法把當前任務存放在ForkJoinTask數組隊列裏。然後再調用ForkJoinPool的signalWork()方法喚醒或創建一個工作線程來執行任務。

final void push(ForkJoinTask<?> task) {
    ForkJoinTask<?>[] a; ForkJoinPool p;
    int b = base, s = top, n;
    if ((a = array) != null) {    // ignore if queue removed
        int m = a.length - 1;     // fenced write for task visibility
        U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
        U.putOrderedInt(this, QTOP, s + 1);
        if ((n = s - b) <= 1) {
            if ((p = pool) != null)
                p.signalWork(p.workQueues, this);
        }
        else if (n >= m)
            growArray();
    }
}

ForkJoinTask的join方法實現原理

Join方法的主要作用是阻塞當前線程並等待獲取結果。讓我們一起看看ForkJoinTask的join方法的實現,

public final V join() {
    int s;
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        reportException(s);
    return getRawResult();
}

它首先調用doJoin方法,通過doJoin()方法得到當前任務的狀態來判斷返回什麼結果,任務狀態有4種:已完成(NORMAL)、被取消(CANCELLED)、信號(SIGNAL)和出現異常(EXCEPTIONAL)。

如果任務狀態是已完成,則直接返回任務結果。

如果任務狀態是被取消,則直接拋出CancellationException

如果任務狀態是拋出異常,則直接拋出對應的異常

讓我們分析一下doJoin方法的實現

private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    return (s = status) < 0 ? s :
    ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
        (w = (wt = (ForkJoinWorkerThread)t).workQueue).
        tryUnpush(this) && (s = doExec()) < 0 ? s :
    wt.pool.awaitJoin(w, this, 0L) :
    externalAwaitDone();
}

final int doExec() {
    int s; boolean completed;
    if ((s = status) >= 0) {
        try {
            completed = exec();
        } catch (Throwable rex) {
            return setExceptionalCompletion(rex);
        }
        if (completed)
            s = setCompletion(NORMAL);
    }
    return s;
}

在doJoin()方法裏,首先通過查看任務的狀態,看任務是否已經執行完成,如果執行完成,則直接返回任務狀態;如果沒有執行完,則從任務數組裏取出任務並執行。如果任務順利執行完成,則設置任務狀態爲NORMAL,如果出現異常,則記錄異常,並將任務狀態設置爲EXCEPTIONAL。

Fork/Join框架的異常處理

ForkJoinTask在執行的時候可能會拋出異常,但是我們沒辦法在主線程裏直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否已經拋出異常或已經被取消了,並且可以通過ForkJoinTask的getException方法獲取異常。getException方法返回Throwable對象,如果任務被取消了則返回CancellationException。如果任務沒有完成或者沒有拋出異常則返回null。

public final Throwable getException() {
    int s = status & DONE_MASK;
    return ((s >= NORMAL)    ? null :
            (s == CANCELLED) ? new CancellationException() :
            getThrowableException());
}
package com.prc.threadDemo1.forkJoinPrc;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 * fork/join demo
 *  需要繼承一個forkJoinTask子類  RecursiveTask<T>,RecursiveAction
 *  重寫compute方法.
 */
public class CountTask extends RecursiveTask<Integer> {

    private static final int THREAD_HOLD = 2;

    private int start;
    private int end;

    public CountTask(int start,int end){
        this.start = start;
        this.end = end;
    }

    /**
     *  重寫 compute方法
     * @return
     */
    @Override
    protected Integer compute() {
        int sum = 0;
        //如果任務足夠小就計算
        boolean canCompute = (end - start) <= THREAD_HOLD;
        if(canCompute){
            for(int i=start;i<=end;i++){
                sum += i;
            }
        }else{
            // 拆解任務的時候需要注意,此處是使用了遞歸的方式。如果拆解任務不當則會產生死循環出產生java.lang.StackOverflowError
            int middle = (start + end) / 2;
            CountTask left = new CountTask(start,middle);
            CountTask right = new CountTask(middle+1,end);
            //執行子任務
            left.fork();
            right.fork();
            //獲取子任務結果
            int lResult = left.join();
            int rResult = right.join();
            sum = lResult + rResult;
        }
        return sum;
    }

    /**
     *  調用方法
     *      創建 ForkJoinPool 實例,和ForkJoinTask.
     *      通過 ForkJoinPool.submit或者ForkJoinPool.excute將和ForkJoinTask執行
     * @param args
     */
    public static void main(String[] args){
        ForkJoinPool pool = new ForkJoinPool();
        CountTask task = new CountTask(1,11);
        Future<Integer> result = pool.submit(task);

        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
// 效率相對高一些的寫法
package com.prc.threadDemo1.forkJoinPrc;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class CountTask2 extends RecursiveTask<Integer> {

    private static final Integer THRESHOLD = 1000;
    private int start;
    private int end;
    public CountTask2(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        Integer sum = 0;
        boolean isOk = end - start <= THRESHOLD;
        if(isOk) {
            for(int i = start; i <= end; i ++) {
                sum += i;
            }
//            System.out.println(String.format("compute %d-%d = %d", start, end, sum));
            return sum;
        }

        //除以2
        int middle = (end + start) / 2;
        //子任務遞歸
//        System.out.println(String.format("fork %d-%d => %d-%d&%d-%d", start, end, start, middle - 1, middle, end));
        CountTask2 sumSubTask = new CountTask2(start, middle - 1);
        CountTask2 sumSubTask1 = new CountTask2(middle, end);

        // fork子任務
        invokeAll(sumSubTask, sumSubTask1);

        //join子任務
        Integer join = sumSubTask.join();
        Integer join1 = sumSubTask1.join();

        sum = join + join1;
        //計算結果
        return sum;
    }

    /**
     * 使用 ForkJoinPool 的invoke方法來啓動任務
     * @param args
     */
    public static void main(String[] args) {
        int start = 1;
        int end = 1000000;

        ForkJoinPool fjp2 = new ForkJoinPool();
        CountTask2 sumTask2 = new CountTask2(start, end);
        long begin3 = System.currentTimeMillis();
        Integer invoke = fjp2.invoke(sumTask2);
        long end3 = System.currentTimeMillis();
        System.out.println("invokeAll 結果爲 sum = " + invoke + ",計算時長爲" + begin3 + "-" + end3 + "---  " + (end3 - begin3) + "ms");
    }
}

線程異常的處理

子線程異常處理問題方案;

  • 子線程中使用try…catch()… 處理。這樣處理後主線程時看不到是否發生了異常

  • 爲線程設置“未捕獲異常處理器”UncaughtExceptionHandler

    package com.prc.threadDemo1.threadExCatch;
    
    public class ThreadExCatchPrc implements Runnable {
    
        public static class ChildThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(String.format("handle exception in child thread. %s", e));
            }
        }
    
        private static ChildThreadExceptionHandler exceptionHandler;
    
        static {
            exceptionHandler = new ChildThreadExceptionHandler();
            Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
        }
    
        public void run() {
            System.out.println("do something 1");
            exceptionMethod();
            System.out.println("do something 2");
        }
    
        private void exceptionMethod() {
            throw new RuntimeException("ChildThread exception");
        }
    }
    private static ChildThreadExceptionHandler exceptionHandler;
    
    static {
        exceptionHandler = new ChildThreadExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
    }
    
    public void run() {
        System.out.println("do something 1");
        exceptionMethod();
        System.out.println("do something 2");
    }
    
    private void exceptionMethod() {
        throw new RuntimeException("ChildThread exception");
    }
    

    }

    
    
    
    
  • 通過Future的get方法捕獲異常

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