Java多线程详解(中)

参考文章
由浅入深理解Java线程池及线程池的如何使用
java常用的几种线程池比较
java线程池与五种常用线程池策略使用与解析

1.并发任务执行器-Executor

1.1 为何要使用Executor

上节中将线程比作货船,一艘货船要报废或者是新买都是一件非常耗费资源的事情,线程的使用也有该问题,当一个线程的任务执行完毕后,该线程会被自然销毁,当有新的任务要驱动时,又必须新建一个新的线程,这个过程对资源消耗是很大的。如下图所示
Java多线程详解(中)_第1张图片

Executor将为你管理Thread对象,从而简化了并发编程,Executor允许你管理异步任务的执行,而无须显式地管理线程的生命周期。
具体实现是Java中引入了线程池的概念来解决复用线程的问题,一个线程驱动完任务后不会被销毁而是进入闲置状态,当有新任务来时闲置线程又可派上用场。

1.2 线程池

线程池的实现类->java.util.concurrent.ThreadPoolExecutor,可用构造方法来创建一个线程池对象

//ThreadPoolExecutor extends AbstractExecutorService
//AbstractExecutorService implements ExecutorService 
//interface ExecutorService extends Executor
 public ThreadPoolExecutor(int corePoolSize, 
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

其中各个参数代表的含义如下

  • corePoolSize:线程池中核心线程的数量,即线程池保持的线程数量,当设置allowCoreThreadTimeOut为true后,核心线程在超时后可变为0
  • maximumPoolSize:线程池允许的最大线程数,当线程数量超过corePoolSize时且阻塞队列(queueCapacity)已满时会新建线程驱动任务
  • keepAliveTime:当线程数量超过核心线程数量时,多余的线程会等待一个时间看是否有新的任务进来,如无则终止多余的线程
  • unit:上述超时时间的单位
  • workQueue:阻塞队列,提交给线程池的任务队列
  • threadFactory:创建一个新线程的工厂
  • handler:表示当拒绝处理任务时的策略。

除了用构造方法创建一个线程池外,Java还提供了一个工厂类java.util.concurrent.Executors来生成配置好的常用线程池方法,以下是它支持创建的四种类型的线程池。

1.2.1 CachedThreadPool 缓存线程池

    //Executors
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }
    //另一重载方法,可传入线程工程对象来决定生成的线程对象
     public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue(),
                                      threadFactory);
    }

CachedThreadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,以达到线程都能被尽量复用的目的

1.2.2 FixedThreadPool 固定线程池

  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }
     public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue(),
                                      threadFactory);
    }

FixedThreadPool会生成固定线程数量的线程池,这样可以一次性预先执行代价高昂的线程分配,也不用为每个任务都固定地付出创建线程的开销。

1.2.3 SingleThreadExecutor 单一线程执行器

   public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue(),
                                    threadFactory));
    }

SingleThreadExecutor就像是线程数量为1的FixedThreadPool,如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队执行,每个任务都会在下一个任务开始之前运行结束,所有的任务都将使用相同的线程

1.2.4 ScheduledThreadPool

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    //ScheduledThreadPoolExecutor
      public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
    
    //执行方法不是execute()
    //延迟执行
     public ScheduledFuture schedule(Runnable command, long delay,TimeUnit unit) 
    //周期执行
     public ScheduledFuture scheduleAtFixedRate(Runnable command,long initialDelay,
     long period, TimeUnit unit) 
      

可让任务延迟或者是周期性地执行

1.2.5 WorkStealingPool

   public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    
        public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

该线程池生成方法是Java8才加入的,创建一个拥有多个任务队列(以便减少连接数)的线程池。
内部会创建ForkJoinPool,利用Work-Stealing算法,并行处理任务,不保证处理顺序。(摘自杨晓峰《Java核心技术36讲》)

1.2.6 前三个线程池示例如下

class LiftOff : Runnable{
    companion object {
        private var  taskCount = 0
    }
    protected  var countDown = 5
    private val id = taskCount++
    constructor(){}
    constructor(countDown : Int){
        this.countDown = countDown
    }

    fun status():String{
       return "#$id(${if (countDown > 0) this.countDown else "LiftOff!"})"
    }
    override fun run() {
        while (countDown-- > 0){
            print(status() + ">"+Thread.currentThread().name+",")
        }
    }

}

   //如不够线程,缓存中又无线程可用,则会创建一个新线程
   val exec = Executors.newCachedThreadPool()
    //固定线程数量
    val exec = Executors.newFixedThreadPool(3)
    //单线程执行
   val exec = Executors.newSingleThreadExecutor()
    for(i in 1..5){
        exec.execute(LiftOff())
    }
    exec.shutdown()
    
/**output
newCachedThreadPool,线程池会创建跟任务数量一样的线程数共五个
#0(4)>pool-1-thread-1,#0(3)>pool-1-thread-1,#0(2)>pool-1-thread-1,#0(1)>pool-1-thread-1,#0(LiftOff!)>pool-1-thread-1,#1(4)>pool-1-thread-2,#2(4)>pool-1-thread-3,#3(4)>pool-1-thread-4,#2(3)>pool-1-thread-3,#3(3)>pool-1-thread-4,#2(2)>pool-1-thread-3,#3(2)>pool-1-thread-4,#2(1)>pool-1-thread-3,#3(1)>pool-1-thread-4,#2(LiftOff!)>pool-1-thread-3,#3(LiftOff!)>pool-1-thread-4,#1(3)>pool-1-thread-2,#1(2)>pool-1-thread-2,#4(4)>pool-1-thread-5,#1(1)>pool-1-thread-2,#4(3)>pool-1-thread-5,#1(LiftOff!)>pool-1-thread-2,#4(2)>pool-1-thread-5,#4(1)>pool-1-thread-5,#4(LiftOff!)>pool-1-thread-5,

newFixedThreadPool,只会创建3个线程
#0(4)>pool-1-thread-1,#0(3)>pool-1-thread-1,#0(2)>pool-1-thread-1,#0(1)>pool-1-thread-1,#0(LiftOff!)>pool-1-thread-1,#3(4)>pool-1-thread-1,#3(3)>pool-1-thread-1,#3(2)>pool-1-thread-1,#3(1)>pool-1-thread-1,#3(LiftOff!)>pool-1-thread-1,#4(4)>pool-1-thread-1,#4(3)>pool-1-thread-1,#4(2)>pool-1-thread-1,#4(1)>pool-1-thread-1,#4(LiftOff!)>pool-1-thread-1,#1(4)>pool-1-thread-2,#1(3)>pool-1-thread-2,#1(2)>pool-1-thread-2,#1(1)>pool-1-thread-2,#1(LiftOff!)>pool-1-thread-2,#2(4)>pool-1-thread-3,#2(3)>pool-1-thread-3,#2(2)>pool-1-thread-3,#2(1)>pool-1-thread-3,#2(LiftOff!)>pool-1-thread-3,

newSingleThreadExecutor,只有一个线程在顺序执行任务
#0(4)>pool-1-thread-1,#0(3)>pool-1-thread-1,#0(2)>pool-1-thread-1,#0(1)>pool-1-thread-1,#0(LiftOff!)>pool-1-thread-1,#1(4)>pool-1-thread-1,#1(3)>pool-1-thread-1,#1(2)>pool-1-thread-1,#1(1)>pool-1-thread-1,#1(LiftOff!)>pool-1-thread-1,#2(4)>pool-1-thread-1,#2(3)>pool-1-thread-1,#2(2)>pool-1-thread-1,#2(1)>pool-1-thread-1,#2(LiftOff!)>pool-1-thread-1,#3(4)>pool-1-thread-1,#3(3)>pool-1-thread-1,#3(2)>pool-1-thread-1,#3(1)>pool-1-thread-1,#3(LiftOff!)>pool-1-thread-1,#4(4)>pool-1-thread-1,#4(3)>pool-1-thread-1,#4(2)>pool-1-thread-1,#4(1)>pool-1-thread-1,#4(LiftOff!)>pool-1-thread-1,

*/

1.3 关闭线程池

通过调用方法

java.util.concurrent.ExecutorService#shutdown()

可防止新任务被提交给这个Executor,当前线程将继续运行在shutdown()被调用之前提交的所有任务。

另一个关闭方法如下,该方法会尝试停止所有正在执行的任务,并返回正在等待执行的任务列表

List shutdownNow();

2.实现有返回值的任务

Runnable是执行工作的独立任务并不返回任何值,如希望在任务完成时返回一个值,那么任务可通过实现Callable接口来实现,Callabel带有一个类型参数的泛型,表示从方法call()中返回值的类型,call()是实现Callable接口需要实现的方法.
实例如下

class TaskWithResult(var id:Int): Callable{

    override fun call(): String {
        return "result of TaskWithResult$id"
    }
}

fun main(args: Array) {
    val exec = Executors.newCachedThreadPool()
    val results = ArrayList>()
    for (i in 0 until 10){
        results.add(exec.submit(TaskWithResult(i)))
    }
    for(fs in results){
        try {
            println(fs.get())
        }catch (e: InterruptedException){
            println(e)
            return
        }catch (e: ExecutionException){
            println(e)
            return
        }finally {
            exec.shutdown()
        }
    }
}
/**output
result of TaskWithResult0
result of TaskWithResult1
result of TaskWithResult2
result of TaskWithResult3
result of TaskWithResult4
result of TaskWithResult5
result of TaskWithResult6
result of TaskWithResult7
result of TaskWithResult8
result of TaskWithResult9
*/

submit()方法会产生Future对象,调用Future的get()方法可获取任务运行的结果,可用isDone()方法来决断任务是否完成,如果任务未完成时调用get()方法,则会阻塞线程,直至结果准备就绪。

3.捕获异常

由于线程的本质特性,当子线程发生异常而没有被捕获时,在主线程中用try-catch没法捕获子线程逃逸的异常,这样就会造成程序异常退出。
但通过Executor执行的任务,可以通过在生成不同线程池方法中可传入的ThreadFactory来捕获子线程的异常。实现过程是在生成每个Thread对象时附着一个异常处理器Thread.UncaughtExceptionThread.UncaughtExecptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。

实例如下

class ExceptionThread2: Runnable{
    override fun run() {
        val t = Thread.currentThread()
        println("run() by $t")
        println("eh = ${t.uncaughtExceptionHandler}")
        throw RuntimeException()
    }
}
class MyUncaughtExceptionHandler: Thread.UncaughtExceptionHandler{
    override fun uncaughtException(t: Thread?, e: Throwable?) {
        println("caught $e") //处理未捕获异常
    }
}

class HandlerThreadFactory: ThreadFactory{
    override fun newThread(r: Runnable?): Thread {
        println("${this}creating new Thread")
        val t = Thread(r)
        println("created$t")
        t.uncaughtExceptionHandler = MyUncaughtExceptionHandler() //这里设置新建线程的未捕获异常处理器
        println("eh = ${t.uncaughtExceptionHandler}")
        return t
    }
}

fun main(args: Array) {
    val exec = Executors.newCachedThreadPool(HandlerThreadFactory())
    exec.execute(ExceptionThread2())
}

/**output
mutilthread.HandlerThreadFactory@266474c2creating new Thread
createdThread[Thread-0,5,main]
eh = mutilthread.MyUncaughtExceptionHandler@6f94fa3e
run() by Thread[Thread-0,5,main]
eh = mutilthread.MyUncaughtExceptionHandler@6f94fa3e
mutilthread.HandlerThreadFactory@266474c2creating new Thread 这是线程池多创建的线程
createdThread[Thread-1,5,main] 
eh = mutilthread.MyUncaughtExceptionHandler@5cded4f1 同时也新建了一个handler的对象
caught java.lang.RuntimeException
*/

实现流程就是自定义ThreadFactory类->自定义UncaughtExceptionHandler类->在ThreadFactory中生成的新线程附着一个UncaughtExceptionHandler对象->未捕获的异常会在自定义UncaughtExceptionHandler类中被处理.
如果想在所有的线程中都实现一样的未捕获异常处理器,则可通过设置Thread的静态域来达到,如下方法所示

Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler)

这个处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用

4.线程状态

一个线程可以处于以下四种状态之一:

  1. 新建(new):当线程被创建时,它只会短暂地处于这种状态。此是它已经分配了必需的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行状态或阻塞状态。
  2. 就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。即是在任意时刻,线程可以运行也可以不运行。只要调度器能根本时间片给线程,它就可以运行,这不同于死亡和阻塞状态
  3. 阻塞(Blocked):线程能够运行,但有某个条件阻止它的运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何CPU时间。直到线程重新进入了就绪状态,它才有可能执行操作。
  4. 死亡(Dead):处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run()方法返回,但是任务的线程还可以被中断。

任务进入阻塞状态的原因

  1. 通过调用sleep(milliseconds)使任务进入休眠状态,在这种情况下,任务在指定的时间内不会运行
  2. 通过调用wait()使线程挂机。直到线程得到了notify()或notifyAll()消息(或者是等价的signal或signalAll()消息),线程才会进入就绪状态。
  3. 任务在等待某个输入/输出完成
  4. 任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获取了这个锁。

以上都是《Java编程思想》中关于线程状态的介绍,但在Java源码中Thread是有六种状态的,

public enum State {
        /**新建状态*/
        NEW,
        
        /**就绪状态*/
        RUNNABLE,
        
         /**阻塞状态*/
        BLOCKED,
        
         /**等待状态*/
        WAITING,
        
         /**超时等待状态*/
        TIMED_WAITING,
        
         /**终止状态*/
        TERMINATED;
    }

这篇文章详细讲解了关于线程状态的切换,
Java线程的6种状态及切换(透彻讲解)

你可能感兴趣的:(JavaSE)