上篇文章对并发的实践根底进行了回顾,次要是为什么应用多线程、多线程会引发什么问题及引发的起因,和怎么应用Java中的多线程去解决这些问题。

正所谓,知其然知其所以然,这是学习一个常识遵循的准则。

举荐读者后行查看并发编程的理论知识,以便能够丝滑入戏。

并发编程系列之一并发实践根底

本篇文章重点在于Java中怎么去应用多线程,和多线程的一些相干概念和操作,及怎么优化多线程。

在Java中每个对象都有其生命周期,线程同样不例外,也有其生命周期。

一、线程生命周期

线程的几种状态转换

1、新建(New)

新创建了一个线程对象,但还没有调用start()办法。

2、就绪

当线程对象调用了start()办法后,该线程就进入就绪状态。处于就绪状态的线程位于线程队列中,此时它只是具备了运行的条件,是否取得CPU的使用权并开始运行,还须要期待零碎的调度。

3、运行(Runnable)

如果处于就绪状态的线程取得了CPU的使用权,并开始执行run()办法中的线程执行体,则该线程处于运行状态。

一个线程启动后,它可能不会始终处于运行状态,当运行状态的线程应用完零碎调配的工夫后,零碎就会剥夺该线程占用的CPU资源,让其余线程取得执行的机会。须要留神的是,

只有处于就绪状态的线程才可能转换到运行状态。

4、阻塞(Blocking)

期待获取一个排它锁,如果其线程开释了锁就会完结此状态。

①无限期期待(Waiting)

期待其它线程显式地唤醒,否则不会被调配 CPU 工夫片。

进入办法退出办法
没有设置 Timeout 参数的 Object.wait() 办法Object.notify() / Object.notifyAll()
没有设置 Timeout 参数的 Thread.join() 办法被调用的线程执行结束
LockSupport.park() 办法-

②限期期待(Timed Waiting)

无需期待其它线程显式地唤醒,在肯定工夫之后会被零碎主动唤醒。

调用 Thread.sleep() 办法使线程进入限期期待状态时,经常用“使一个线程睡眠”进行形容。

调用 Object.wait() 办法使线程进入限期期待或者无限期期待时,经常用“挂起一个线程”进行形容。

睡眠和挂起是用来形容行为,而阻塞和期待用来形容状态。

阻塞和期待的区别在于,阻塞是被动的,它是在期待获取一个排它锁。而期待是被动的,通过调用 Thread.sleep() 和 Object.wait() 等办法进入。

进入办法退出办法
Thread.sleep() 办法工夫完结
设置了 Timeout 参数的 Object.wait() 办法工夫完结 / Object.notify() / Object.notifyAll()
设置了 Timeout 参数的 Thread.join() 办法工夫完结 / 被调用的线程执行结束
LockSupport.parkNanos() 办法-
LockSupport.parkUntil() 办法-

5、死亡(Terminated)

如果线程调用stop()办法或nun()办法失常执行结束,或者线程抛出一个未捕捉的异样(Exception)谬误(Error),线程就进入死亡状态。一旦进入死亡状态,线程将不再领有运行的资格,也不能再转换到其余状态。

了解线程的五种状态,在调用多线程的办法时,能分明的晓得以后处于哪个状态。

咱们举一个简略的实例来阐明每个状态。

public class MyThread extends Thread {        //运行状态    public void run() {        // ...    }        public static void main(String[] args) {    MyThread mt = new MyThread(); //1、新建状态    mt.start(); //就绪状态}}     

在线程管制章节有一些办法,如sleep()\join()办法,这些办法会让线程处于阻塞状态。

理解了线程的生成周期当前,接下来咱们就须要把握在Java中怎么应用多线程。

在Java中有三种形式实现多线程。

二、创立线程的三种形式

有三种应用线程的办法:

  • 实现 Runnable 接口;
  • 实现 Callable 接口;
  • 继承 Thread 类。

实现 Runnable 和 Callable 接口的类只能当做一个能够在线程中运行的工作,不是真正意义上的线程,因而最初还须要通过 Thread 来调用。能够说工作是通过线程驱动从而执行的。

1、实现 Runnable 接口

须要实现 run() 办法。

通过 Thread 调用 start() 办法来启动线程。

public class MyRunnable implements Runnable {    public void run() {        // 须要执行多线程的业务逻辑    }}    
public static void main(String[] args) {    MyRunnable myRunnable = new MyRunnable();    Thread thread = new Thread(myRunnable);    thread.start();}     

2、 实现 Callable 接口

与 Runnable 相比,Callable 能够有返回值,返回值通过 FutureTask 进行封装。

public class MyCallable implements Callable<Integer> {    public Integer call() {        return 123;    }}  
public static void main(String[] args) throws ExecutionException, InterruptedException {    MyCallable mc = new MyCallable();    FutureTask<Integer> ft = new FutureTask<>(mc);    Thread thread = new Thread(ft);    thread.start();    System.out.println(ft.get());}  

3、继承 Thread 类

同样也是须要实现 run() 办法,因为 Thread 类也实现了 Runable 接口。

当调用 start() 办法启动一个线程时,虚构机会将该线程放入就绪队列中期待被调度,当一个线程被调度时会执行该线程的 run() 办法。

public class MyThread extends Thread {    public void run() {        // ...    }}           
public static void main(String[] args) {    MyThread mt = new MyThread();    mt.start();}         

4、实现接口 VS 继承 Thread

实现接口会更好一些,因为:

  • Java 不反对多重继承,因而继承了 Thread 类就无奈继承其它类,然而能够实现多个接口;
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。

三、线程管制

线程在应用过程中能对其灵便的管制,蕴含线程睡眠和线程退让等。

在学习线程的一些管制办法前,有一个必须要理解的前置常识,在线程中分为守护过程和非守护过程。

1、Daemon

守护线程是程序运行时在后盾提供服务的线程,不属于程序中不可或缺的局部。

当所有非守护线程完结时,程序也就终止,同时会杀死所有守护线程。

垃圾回收线程就是一个经典的守护线程,当咱们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会主动来到。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

main() 属于非守护线程。

非守护线程能够转换为守护过程。

应用 setDaemon() 办法将一个线程设置为守护线程。

public static void main(String[] args) {    Thread thread = new Thread(new MyRunnable());    thread.setDaemon(true);}  

2、sleep()

Thread.sleep(millisec) 办法会休眠以后正在执行的线程,millisec 单位为毫秒。

sleep() 可能会抛出 InterruptedException,因为异样不能跨线程流传回 main() 中,因而必须在本地进行解决。线程中抛出的其它异样也同样须要在本地进行解决。

public void run() {    try {        Thread.sleep(3000);    } catch (InterruptedException e) {        e.printStackTrace();    }}      

3、yield()

对静态方法 Thread.yield() 的调用申明了以后线程曾经实现了生命周期中最重要的局部,能够切换给其它线程来执行。该办法只是对线程调度器的一个倡议,而且也只是倡议具备雷同优先级的其它线程能够运行。

public void run() {    Thread.yield();}  

4、join()

一旦这个线程执行了这个办法,只有这个线程处于死亡状态其余线程能力执行。

public class MyThread extends Thread {1112     public MyThread() {13     }1415     public MyThread(String name) {16         super(name);17     }1819     @Override20     public void run() {21         for (int i = 0; i < 10; i++) {22             System.out.println(getName() + ":" + i);23         }24     }2526     public static void main(String[] args) {27         // 1.创立MyThread类的对象28         MyThread myThread1 = new MyThread("线程1");29         MyThread myThread2 = new MyThread("线程2");30         MyThread myThread3 = new MyThread("线程3");3132         // 2.启动线程33         myThread1.start();34         try {35             // 期待myThread1线程死亡,只有当该线程死亡之后能力继续执行其它线程36             myThread1.join();37         } catch (InterruptedException e) {38             e.printStackTrace();39         }40         myThread2.start();41         myThread3.start();4243     }44 }

5、wait()\notify()

wait\notify\notifyAll操作都是属于Object类提供的办法,即所有的对象都具备该办法,他们是的一对的,调用的时候不能离开呦。

wait():调用wait办法的线程,以后持有锁的该线程期待,直至该对象的另一个持锁线程调用notify/notifyAll操作。
wait(long timeOut)、wait(long timeOut,int nanos)

线程状态转换是,当wait被唤醒或超时,并不是间接进入到运行或者就绪状态,而是先进入到Block状态,抢锁胜利后,能力进入到可运行状态。

wait办法在调用进入阻塞之前会开释锁,而sleep或join是不会开释锁的

notify():告诉持有该对象锁的所有线程中的的随便一个线程被唤醒

notifyAll():告诉持有该对象锁的所有线程被同时唤醒

咱们形象的做一个比喻:

如果把多线程比喻成一个运动员,跑道就是CPU每次只能容许一个运动员进入跑道,运动员的后勤保障就是守护过程,通过setDaemon()办法,运动员就转业为了后勤人员。

执行sleep()就是提前设定一个工夫,让运动员劳动会。wait()办法是运动员无限期的睡着,直到教练杀进去一脚踹醒(执行notify办法)运动员才会唤醒。

yield()会把跑道让给别的运动员。

join()办法会让运动员领有最高的跑道权限,我不跑完,谁都不能进来。

四、线程同步

Java容许并发管制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不精确,相互之间产生抵触,因而退出同步锁以防止在该线程没有实现操作之前,被其余线程的调用, 从而保障了该变量的唯一性和准确性。

Java 提供了两种锁机制来管制多个线程对共享资源的互斥拜访,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

1、synchronized

①. 同步一个代码块

public void func() {    synchronized (this) {        // ...    }}        

它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

对于以下代码,应用 ExecutorService 执行了两个线程,因为调用的是同一个对象的同步代码块,因而这两个线程会进行同步,当一个线程进入同步语句块时,另一个线程就必须期待。

public class SynchronizedExample {    public void func1() {        synchronized (this) {            for (int i = 0; i < 10; i++) {                System.out.print(i + " ");            }        }    }}  public static void main(String[] args) {    SynchronizedExample e1 = new SynchronizedExample();    ExecutorService executorService = Executors.newCachedThreadPool();    executorService.execute(() -> e1.func1());    executorService.execute(() -> e1.func1());}        
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9         

对于以下代码,两个线程调用了不同对象的同步代码块,因而这两个线程就不须要同步。从输入后果能够看出,两个线程穿插执行。

public static void main(String[] args) {    SynchronizedExample e1 = new SynchronizedExample();    SynchronizedExample e2 = new SynchronizedExample();    ExecutorService executorService = Executors.newCachedThreadPool();    executorService.execute(() -> e1.func1());    executorService.execute(() -> e2.func1());}        
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9       

②. 同步一个办法

public synchronized void func () {    // ...}         

它和同步代码块一样,作用于同一个对象。

③. 同步一个类

public void func() {    synchronized (SynchronizedExample.class) {        // ...    }}         

作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。

public class SynchronizedExample {    public void func2() {        synchronized (SynchronizedExample.class) {            for (int i = 0; i < 10; i++) {                System.out.print(i + " ");            }        }    }}  
public static void main(String[] args) {    SynchronizedExample e1 = new SynchronizedExample();    SynchronizedExample e2 = new SynchronizedExample();    ExecutorService executorService = Executors.newCachedThreadPool();    executorService.execute(() -> e1.func2());    executorService.execute(() -> e2.func2());}      

④. 同步一个静态方法

public synchronized static void fun() {    // ...}      

作用于整个类。

2、ReentrantLock

ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。

public class LockExample {    private Lock lock = new ReentrantLock();    public void func() {        lock.lock();        try {            for (int i = 0; i < 10; i++) {                System.out.print(i + " ");            }        } finally {            lock.unlock(); // 确保开释锁,从而防止产生死锁。        }    }}   
public static void main(String[] args) {    LockExample lockExample = new LockExample();    ExecutorService executorService = Executors.newCachedThreadPool();    executorService.execute(() -> lockExample.func());    executorService.execute(() -> lockExample.func());}     
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9    

3、比拟

①. 锁的实现**

synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。

②. 性能

新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。

③. 期待可中断

当持有锁的线程长期不开释锁的时候,正在期待的线程能够抉择放弃期待,改为解决其余事件。

ReentrantLock 可中断,而 synchronized 不行。

④. 偏心锁

偏心锁是指多个线程在期待同一个锁时,必须依照申请锁的工夫程序来顺次取得锁。

synchronized 中的锁是非偏心的,ReentrantLock 默认状况下也是非偏心的,然而也能够是偏心的。

⑤. 锁绑定多个条件

一个 ReentrantLock 能够同时绑定多个 Condition 对象。

4、应用抉择

除非须要应用 ReentrantLock 的高级性能,否则优先应用 synchronized。

这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地反对它,而 ReentrantLock 不是所有的 JDK 版本都反对。

并且应用 synchronized 不必放心没有开释锁而导致死锁问题,因为 JVM 会确保锁的开释。

 如果并发的线程数量很多,并且每个线程都是执行一个工夫很短的工作就完结了,这样频繁创立线程就会大大降低零碎的效率,因为频繁创立线程和销毁线程须要工夫。

线程池就利用而生。

五、线程池

线程池围绕着一个外围的类 java.uitl.concurrent.ThreadPoolExecutor,咱们将它作为一个切入点揭开线程池的面纱。

1、外围线程类

 java.uitl.concurrent.ThreadPoolExecutor类是线程池中最外围的一个类,因而如果要透彻地理解Java中的线程池,必须先理解这个类。上面咱们来看一下ThreadPoolExecutor类的具体实现源码。

在ThreadPoolExecutor类中有四个构造方法。

其中三个最终都是调用了上面这个构造方法,限于篇幅就不在贴其余三个源码了,读者能够进行求证。

    public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) {        if (corePoolSize < 0 ||            maximumPoolSize <= 0 ||            maximumPoolSize < corePoolSize ||            keepAliveTime < 0)            throw new IllegalArgumentException();        if (workQueue == null || threadFactory == null || handler == null)            throw new NullPointerException();        this.acc = System.getSecurityManager() == null ?                null :                AccessController.getContext();        this.corePoolSize = corePoolSize;        this.maximumPoolSize = maximumPoolSize;        this.workQueue = workQueue;        this.keepAliveTime = unit.toNanos(keepAliveTime);        this.threadFactory = threadFactory;        this.handler = handler;    }

 上面解释下一下结构器中各个参数的含意:

  • corePoolSize:外围池的大小,这个参数跟前面讲述的线程池的实现原理有十分大的关系。在创立了线程池后,默认状况下,线程池中并没有任何线程,而是期待有工作到来才创立线程去执行工作,除非调用了prestartAllCoreThreads()或者prestartCoreThread()办法,从这2个办法的名字就能够看出,是预创立线程的意思,即在没有工作到来之前就创立corePoolSize个线程或者一个线程。默认状况下,在创立了线程池后,线程池中的线程数为0,当有工作来之后,就会创立一个线程去执行工作,当线程池中的线程数目达到corePoolSize后,就会把达到的工作放到缓存队列当中;
  • maximumPoolSize:线程池最大线程数,这个参数也是一个十分重要的参数,它示意在线程池中最多能创立多少个线程;
  • keepAliveTime:示意线程没有工作执行时最多放弃多久工夫会终止。默认状况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程闲暇的工夫达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。然而如果调用了allowCoreThreadTimeOut(boolean)办法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:参数keepAliveTime的工夫单位,有7种取值,在TimeUnit类中有7种动态属性:

    TimeUnit.DAYS;               //天TimeUnit.HOURS;             //小时TimeUnit.MINUTES;           //分钟TimeUnit.SECONDS;           //秒TimeUnit.MILLISECONDS;      //毫秒TimeUnit.MICROSECONDS;      //奥妙TimeUnit.NANOSECONDS;       //纳秒
  • workQueue:一个阻塞队列,用来存储期待执行的工作,这个参数的抉择也很重要,会对线程池的运行过程产生重大影响,一般来说,察看传入的workQueue 都是默认,即最大可增加Integer.MAX_VALUE个工作,所有在应用过程中要防止应用默认线程池。这里的阻塞队列有以下几种抉择:

    ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue;ArrayBlockingQueue和PriorityBlockingQueue应用较少,个别应用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue无关。
  • threadFactory:线程工厂,次要用来创立线程;
  • handler:示意当回绝解决工作时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:抛弃工作并抛出RejectedExecutionException异样。 ThreadPoolExecutor.DiscardPolicy:也是抛弃工作,然而不抛出异样。 ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列最后面的工作,而后从新尝试执行工作(反复此过程)ThreadPoolExecutor.CallerRunsPolicy:由调用线程解决该工作 

以上对结构的七个参数进行了介绍,那么这些参数是怎么起作用的呢,咱们接着看线程池的执行流程。

2、线程执行流程

  1. 当线程池小于corePoolSize时,新提交工作将创立一个新线程执行工作,即便此时线程池中存在闲暇线程。
  2. 当线程池达到corePoolSize时,新提交工作将被放入workQueue中,期待线程池中任务调度执行
  3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交工作会创立新线程执行工作
  4. 当提交工作数超过maximumPoolSize时,新提交工作由RejectedExecutionHandler解决
  5. 当线程池中超过corePoolSize线程,闲暇工夫达到keepAliveTime时,开释闲暇线程
  6. 当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程闲暇工夫达到keepAliveTime也将敞开

3、四种线程池及应用场景

Java通过Executors提供四种线程池,别离为

  1. newSingleThreadExecutor 创立一个单线程化的线程池,它只会用惟一的工作线程来执行工作,保障所有工作依照指定程序(FIFO, LIFO, 优先级)执行。
  2. newFixedThreadPool 创立一个定长线程池,可控制线程最大并发数,超出的线程会在队列中期待。
  3. newScheduledThreadPool 创立一个可定期或者延时执行工作的定长线程池,反对定时及周期性工作执行。
  4. newCachedThreadPool 创立一个可缓存线程池,如果线程池长度超过解决须要,可灵便回收闲暇线程,若无可回收,则新建线程。

newCachedThreadPool:

  • 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;工夫单位TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
  • 艰深:当有新工作到来,则插入到SynchronousQueue中,因为SynchronousQueue是同步队列,因而会在池中寻找可用线程来执行,若有能够线程则执行,若没有可用线程则创立一个线程来执行该工作;若池中线程闲暇工夫超过指定工夫,则该线程会被销毁。
  • 实用:执行很多短期的异步工作

    /** * 1.创立一个可缓存的线程池。如果线程池的大小超过了解决工作所须要的线程,那么就会回收局部闲暇(60秒不执行工作)的线程<br> * 2.当工作数减少时,此线程池又能够智能的增加新线程来解决工作<br> * 3.此线程池不会对线程池大小做限度,线程池大小齐全依赖于操作系统(或者说JVM)可能创立的最大线程大小<br> */public static void cacheThreadPool() {        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();        for (int i = 1; i <= 10; i++) {            final int ii = i;            try {                Thread.sleep(ii * 1);            } catch (InterruptedException e) {                e.printStackTrace();            }            cachedThreadPool.execute(()->out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii));        }}-----output------线程名称:pool-1-thread-1,执行1线程名称:pool-1-thread-1,执行2线程名称:pool-1-thread-1,执行3线程名称:pool-1-thread-1,执行4线程名称:pool-1-thread-1,执行5线程名称:pool-1-thread-1,执行6线程名称:pool-1-thread-1,执行7线程名称:pool-1-thread-1,执行8线程名称:pool-1-thread-1,执行9线程名称:pool-1-thread-1,执行10

newFixedThreadPool:

  • 底层:返回ThreadPoolExecutor实例,接管参数为所设定线程数量n,corePoolSize和maximumPoolSize均为n;keepAliveTime为0L;工夫单位TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无界阻塞队列
  • 艰深:创立可包容固定数量线程的池子,每个线程的存活工夫是有限的,当池子满了就不再增加线程了;如果池中的所有线程均在忙碌状态,对于新工作会进入阻塞队列中(无界的阻塞队列)
  • 实用:执行长期工作

     /**  * 1.创立固定大小的线程池。每次提交一个工作就创立一个线程,直到线程达到线程池的最大大小<br>  * 2.线程池的大小一旦达到最大值就会放弃不变,如果某个线程因为执行异样而完结,那么线程池会补充一个新线程<br>  * 3.因为线程池大小为3,每个工作输入index后sleep 2秒,所以每两秒打印3个数字,和线程名称<br>  */public static void fixTheadPoolTest() {        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);        for (int i = 0; i < 10; i++) {            final int ii = i;            fixedThreadPool.execute(() -> {                out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii);                try {                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            });        }}------output-------线程名称:pool-1-thread-3,执行2线程名称:pool-1-thread-1,执行0线程名称:pool-1-thread-2,执行3线程名称:pool-1-thread-3,执行4线程名称:pool-1-thread-1,执行5线程名称:pool-1-thread-2,执行6线程名称:pool-1-thread-3,执行7线程名称:pool-1-thread-1,执行8线程名称:pool-1-thread-3,执行9

newSingleThreadExecutor:

  • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;工夫单位TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
  • 艰深:创立只有一个线程的线程池,当该线程正忙碌时,对于新工作会进入阻塞队列中(无界的阻塞队列)
  • 实用:按程序执行工作的场景
/**  *创立一个单线程化的线程池,它只会用惟一的工作线程来执行工作,保障所有工作依照指定程序(FIFO, LIFO, 优先级)执行 */public static void singleTheadPoolTest() {        ExecutorService pool = Executors.newSingleThreadExecutor();        for (int i = 0; i < 10; i++) {            final int ii = i;            pool.execute(() -> out.println(Thread.currentThread().getName() + "=>" + ii));        }}-----output-------

线程名称:pool-1-thread-1,执行0
线程名称:pool-1-thread-1,执行1
线程名称:pool-1-thread-1,执行2
线程名称:pool-1-thread-1,执行3
线程名称:pool-1-thread-1,执行4
线程名称:pool-1-thread-1,执行5
线程名称:pool-1-thread-1,执行6
线程名称:pool-1-thread-1,执行7
线程名称:pool-1-thread-1,执行8
线程名称:pool-1-thread-1,执行9

NewScheduledThreadPool:

  • 底层:创立ScheduledThreadPoolExecutor实例,该对象继承了ThreadPoolExecutor,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;工夫单位TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时工夫升序排序的队列
  • 艰深:创立一个固定大小的线程池,线程池内线程存活工夫无限度,线程池能够反对定时及周期性工作执行,如果所有线程均处于忙碌状态,对于新工作会进入DelayedWorkQueue队列中,这是一种依照超时工夫排序的队列构造
  • 实用:执行周期性工作
/** * 创立一个定长线程池,反对定时及周期性工作执行。提早执行 */public static void sceduleThreadPool() {        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);        Runnable r1 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:3秒后执行");        scheduledThreadPool.schedule(r1, 3, TimeUnit.SECONDS);        Runnable r2 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:提早2秒后每3秒执行一次");        scheduledThreadPool.scheduleAtFixedRate(r2, 2, 3, TimeUnit.SECONDS);        Runnable r3 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:一般工作");        for (int i = 0; i < 5; i++) {            scheduledThreadPool.execute(r3);        }}----output------线程名称:pool-1-thread-1,执行:一般工作线程名称:pool-1-thread-5,执行:一般工作线程名称:pool-1-thread-4,执行:一般工作线程名称:pool-1-thread-3,执行:一般工作线程名称:pool-1-thread-2,执行:一般工作线程名称:pool-1-thread-1,执行:提早2秒后每3秒执行一次线程名称:pool-1-thread-5,执行:3秒后执行线程名称:pool-1-thread-4,执行:提早2秒后每3秒执行一次线程名称:pool-1-thread-4,执行:提早2秒后每3秒执行一次线程名称:pool-1-thread-4,执行:提早2秒后每3秒执行一次线程名称:pool-1-thread-4,执行:提早2秒后每3秒执行一次

5、应用实例

在ThreadPoolTaskExecutor的原理章节中,有一系列的办法,如果咱们手动调用这些线程池办法实现办法是极其简单的。

①、在java中的应用

public class Test {     public static void main(String[] args) {            ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,                 new ArrayBlockingQueue<Runnable>(5));                   for(int i=0;i<15;i++){             MyTask myTask = new MyTask(i);             executor.execute(myTask);             System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中期待执行的工作数目:"+             executor.getQueue().size()+",已执行玩别的工作数目:"+executor.getCompletedTaskCount());         }         executor.shutdown();     }}  class MyTask implements Runnable {    private int taskNum;         public MyTask(int num) {        this.taskNum = num;    }         @Override    public void run() {        System.out.println("正在执行task "+taskNum);        try {            Thread.currentThread().sleep(4000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("task "+taskNum+"执行结束");    }}

从执行后果能够看出,当线程池中线程的数目大于5时,便将工作放入工作缓存队列外面,当工作缓存队列满了之后,便创立新的线程。如果下面程序中,将for循环中改成执行20个工作,就会抛出工作回绝异样了。

  不过在java doc中,并不提倡咱们间接应用ThreadPoolExecutor,而是应用Executors类中提供的几个静态方法来创立线程池:

Executors.newCachedThreadPool();    //创立一个缓冲池,缓冲池容量大小为Integer.MAX_VALUEExecutors.newSingleThreadExecutor();  //创立容量为1的缓冲池Executors.newFixedThreadPool(int);  //创立固定容量大小的缓冲池

 从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。

  newFixedThreadPool创立的线程池corePoolSize和maximumPoolSize值是相等的,它应用的LinkedBlockingQueue;

  newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也应用的LinkedBlockingQueue;

  newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,应用的SynchronousQueue,也就是说来了工作就创立线程运行,当线程闲暇超过60秒,就销毁线程。

  理论中,如果Executors提供的三个静态方法能满足要求,就尽量应用它提供的三个办法,因为本人去手动配置ThreadPoolExecutor的参数有点麻烦,要依据理论工作的类型和数量来进行配置。

  另外,如果ThreadPoolExecutor达不到要求,能够本人继承ThreadPoolExecutor类进行重写。

②、在Spring中应用

以下为Java线程池在Spring中的应用,ThreadPoolTaskExecutor一个对象注入到Spring的容器中。

/** * 线程池配置 * * @author tcy **/@Configurationpublic class ThreadPoolConfig {  // 外围线程池大小  private final int corePoolSize = 50;  // 最大可创立的线程数  private final int maxPoolSize = 200;  // 队列最大长度  private final int queueCapacity = 1000;  // 线程池保护线程所容许的闲暇工夫  private final int keepAliveSeconds = 300;  @Bean(name = "threadPoolTaskExecutor")  public ThreadPoolTaskExecutor threadPoolTaskExecutor() {    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();    executor.setMaxPoolSize(maxPoolSize);    executor.setCorePoolSize(corePoolSize);    executor.setQueueCapacity(queueCapacity);    executor.setKeepAliveSeconds(keepAliveSeconds);    // 线程池对回绝工作(无线程可用)的解决策略    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());    return executor;  }

在办法或者类上加 @Async注解,表明该办法或类为多线程办法,Spirng外部会主动调用多线程的回绝策略、线程初始化等办法。