Java多线程的总结

如何实现线程

三种方式:

  • 继承Thread

    public MyThread extends Thread{}
    
  • 实现runnable 无返回值的

    class MyTask implement runnable{
    
    }
    
  • 实现callable 有返回值 用Future接口的实现类FutureTask进行接收

    class MyTask<T> implement callable<T>{
    //指定的泛型是返回值的类型
    }
    

线程池

线程是一种珍贵的资源,频繁的创建线程会造成效率降低

所以不用的线程保留回收是必要的,而且通过线程池创建的方式很容易管理,再指定产生线程ThreadFactory后就很容易排查出现问题的线程

我们先来看一下线程池创建的几个参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • int corePoolSize 核心线程数

  • int maximumPoolSize 最大线程数

  • long keepAliveTime 超过核心线程数的线程多长时间未使用会被回收

  • TimeUnit unit 上面回收时间的单位

  • BlockingQueue workQueue 任务队列

  • ThreadFactory threadFactory 执行生产线程的方式

  • RejectedExecutionHandler handler 拒绝策略 Java一共提供了四种拒绝策略

    • AbortPolicy 报异常

    • DiscardOldestPolicy 丢弃最早的

    • DiscardPolicy 直接丢弃

    • CallerRunsPolicy 谁给的任务谁去执行

Java中一共提供三种类型的线程池

  • 第一种是singleThreadExecutorPool顾名思义这是一种只有一个线程的线程池,阻塞队列利用的是LinkedBlockingQueue,这种队列是有界队列,有人可能会问了,linkedBlockingQueue不是链表实现的吗,为什么会是有界的呢,这是因为内部做了限制,最大为Integer.MAX_VALUE也就是2的31次方减一,为啥是2的31次方呢,因为int类型是32位嘛,但是呢,最高位是表示正负的,所以这样一来就剩31位可用了。适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
  • 第二种是FixedThreadExecutorPool,这种根据名称一看就是固定线程数量的线程池,它的coresize与maxsize是一致的,也就是说当核心队列与任务队列都满了之后呢还有任务提交就执行解决策略了。它的任务队列也是LinkedBlockingQueue,适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
  • 第三种是CachedTheadExecutor线程池,这种的话因为拿的阻塞队列是SynchronousQueue这种队列有一个特点就是不储存值,这样依赖的话提交的任务就要立马执行,然后呢,它将coresize设置为0,maxsize设置为Integer.MAX_VALUE,这样一来,就是来一个任务创建一个线程,这样的线程池的可重复利用线程的优势就没了,为什么这么做呢,请注意他还有一个参数是60s不用才会被回收如果说一个线程执行完任务后60s内还有任务过来就会继续复用,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

线程池任务的执行经过以下几个步骤

  • 若线程池的核心线程数没有满,就开启核心线程
  • 核心线程满了就放入任务队列也就是指定的阻塞队列中去
  • 阻塞队列也满的话就开启非核心线程执行
  • 如果非核心线程也达到的最大值的话就执行拒绝策略

任务提交的几种方式

  • 第一种是execute(task)方法,只能执行Runnable类型的任务,也是submit的底层,有异常就抛出

  • 第二种是submit(task)方法,它可以执行runnale和callable方法,实际上也是runnable类型,因为吧callable类型的任务封装成了Runnable类型的,然后提交给execute执行,有异常在get()时会抛出

    1     public <T> Future<T> submit(Callable<T> task) {
    2         if (task == null) throw new NullPointerException();
    3         RunnableFuture<T> ftask = newTaskFor(task);
    4         execute(ftask);
    5         return ftask;
    6     }
    

callable的具体运行过程

以下内容来自https://www.cnblogs.com/by-my-blog/p/10779333.html

首先看看FutureRunnable的run方法,因为他是runnable任务,被execute提交后肯定会运行这个任务的run方法

 1 public void run() {
 2         if (state != NEW ||
 3             !UNSAFE.compareAndSwapObject(this, runnerOffset,
 4                                          null, Thread.currentThread()))
 5             return;
 6         try {
 7             Callable<V> c = callable;
 8             if (c != null && state == NEW) {
 9                 V result;
10                 boolean ran;
11                 try {
12                     result = c.call();
13                     ran = true;//执行成功就会将标志为设置为true
14                 } catch (Throwable ex) {    //捕获所有异常
15                     result = null;
16                     ran = false;//出现异常就设置为false
17                     setException(ex);       //有异常就保存异常
18                 }
19                 if (ran)
20                     set(result);             //没有异常就设置返回值
21             }
22         } finally {
23             // runner must be non-null until state is settled to
24             // prevent concurrent calls to run()
25             runner = null;
26             // state must be re-read after nulling runner to prevent
27             // leaked interrupts
28             int s = state;
29             if (s >= INTERRUPTING)
30                 handlePossibleCancellationInterrupt(s);
31         }
32     }

可以看到,runnable 的 run 方法里,直接调用了他封装的 callable 任务的 call()方法 ,如果有异常,就直接将异常放入 这个类的静态变量里 ,如果没有异常,就将返回值放入自己的局部变量里,我们来看看上面代码中的Set(result)方法吧

1       protected void set(V v) {
2         if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
3             outcome = v;
4             UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
5             finishCompletion();
6         }
7     }

可以看到标红的部分,其实就是将传进来的值,保存到一个叫做 outcome 的静态变量里面了,而相对应的,由于一开始提交任务时返回了本类的引用(Future对象),所以可以通过引用访问静态变量的方式,访问到返回值了,Future.get() 在RunnableFuture中的实现如下

1     public V get() throws InterruptedException, ExecutionException {
2         int s = state;
3         if (s <= COMPLETING)  //如果线程还没运行完成
4             s = awaitDone(false, 0L);  //阻塞等待
5         return report(s);      //返回值或者异常
6     }

上面代码中的report(s)实现如下:

    private V report(int s) throws ExecutionException {
        Object x = outcome;      
        if (s == NORMAL)        
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

现在我们再总结一下吧:首先callable没有什么神奇之处,通过submit提交之后,会被包装成RunnableFuture,同时被当作返回值传回,在RunnableFuture的run方法中,会调用它保存的callable任务的call方法,同时跟据是否有异常,来决定保存返回值或者异常到其静态变量中,最后外部通过get方法就可以访问到返回值啦!

小技巧:在使用future.get() 获取返回值的时候,如果当前这个值还没有计算出来,那么就会产生阻塞,直到获取到值,这个时候我们可以用 future.isDone 检查任务的状态,再根据状态去get这个值,这样就不会阻塞了

posted @ 2020-12-04 19:09  小鸡小鸡快点跑  阅读(100)  评论(0)    收藏  举报