上篇文章对并发的实践根底进行了回顾,次要是为什么应用多线程、多线程会引发什么问题及引发的起因,和怎么应用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、线程执行流程
- 当线程池小于corePoolSize时,新提交工作将创立一个新线程执行工作,即便此时线程池中存在闲暇线程。
- 当线程池达到corePoolSize时,新提交工作将被放入workQueue中,期待线程池中任务调度执行
- 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交工作会创立新线程执行工作
- 当提交工作数超过maximumPoolSize时,新提交工作由RejectedExecutionHandler解决
- 当线程池中超过corePoolSize线程,闲暇工夫达到keepAliveTime时,开释闲暇线程
- 当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程闲暇工夫达到keepAliveTime也将敞开
3、四种线程池及应用场景
Java通过Executors提供四种线程池,别离为
- newSingleThreadExecutor 创立一个单线程化的线程池,它只会用惟一的工作线程来执行工作,保障所有工作依照指定程序(FIFO, LIFO, 优先级)执行。
- newFixedThreadPool 创立一个定长线程池,可控制线程最大并发数,超出的线程会在队列中期待。
- newScheduledThreadPool 创立一个可定期或者延时执行工作的定长线程池,反对定时及周期性工作执行。
- 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外部会主动调用多线程的回绝策略、线程初始化等办法。