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方法捕获异常

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值