Java多线程开发系列-线程管理
主题:
- 线程的未捕获异常
- 线程工厂
- 线程暂停
- 线程池
线程的未捕获异常
在线程异常的时候,多线程运行不能按照顺序执行过程中捕获异常的方式来处理异常,异常会被直接抛出到控制台(由于线程的本质,使得你不能捕获从线程中逃逸的异常。一旦异常逃逸出任务的run方法,它就会向外传播到控制台,除非你采用特殊的形式捕获这种异常。)
如下例:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadException implements Runnable{ @Override public void run() { throw new RuntimeException(); } //现象:控制台打印出异常信息,并运行一段时间后才停止 public static void main(String[] args){ //就算把线程的执行语句放到try-catch块中也无济于事 try{ ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new ThreadException()); }catch(RuntimeException e){ System.out.println("Exception has been handled!"); } } }
使用UncaughtExceptionHandler可以捕获异常,并且重启新的线程来完成redo工作,这个例子里是重新初始化:init();
import io.github.viscent.mtia.util.Debug; import io.github.viscent.mtia.util.Tools; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; public class ThreadMonitorDemo { volatile boolean inited = false; static int threadIndex = 0; final static Logger LOGGER = Logger.getAnonymousLogger(); final BlockingQueue<String> channel = new ArrayBlockingQueue<String>(100); public static void main(String[] args) throws InterruptedException { ThreadMonitorDemo demo = new ThreadMonitorDemo(); demo.init(); for (int i = 0; i < 100; i++) { demo.service("test-" + i); } Thread.sleep(2000); System.exit(0); } public synchronized void init() { if (inited) { return; } Debug.info("init..."); WokrerThread t = new WokrerThread(); t.setName("Worker0-" + threadIndex++); // 为线程t关联一个UncaughtExceptionHandler t.setUncaughtExceptionHandler(new ThreadMonitor()); t.start(); inited = true; } public void service(String message) throws InterruptedException { channel.put(message); } private class ThreadMonitor implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { Debug.info("Current thread is `t`:%s, it is still alive:%s", Thread.currentThread() == t, t.isAlive()); // 将线程异常终止的相关信息记录到日志中 String threadInfo = t.getName(); LOGGER.log(Level.SEVERE, threadInfo + " terminated:", e); // 创建并启动替代线程 LOGGER.info("About to restart " + threadInfo); // 重置线程启动标记 inited = false; init(); } }// 类ThreadMonitor定义结束 private class WokrerThread extends Thread { @Override public void run() { Debug.info("Do something important..."); String msg; try { for (;;) { msg = channel.take(); process(msg); } } catch (InterruptedException e) { // 什么也不做 } } private void process(String message) { Debug.info(message); // 模拟随机性异常 int i = (int) (Math.random() * 100); if (i < 2) { throw new RuntimeException("test"); } Tools.randomPause(100); } }// 类ThreadMonitorDemo定义结束 }
线程工厂
工厂设计模式是java中最常用的设计模式之一。它是一种创造性的模式,可用于开发一个或多个类需要的对象。有了这个工厂,我们可以集中创建对象。
创造逻辑的集中给我们带来了一些优势,例如:
很容易更改所创建对象的类或创建这些对象的方式。
很容易为有限的资源限制对象的创建。例如,我们只能有N个类型的对象。
很容易生成关于对象创建的统计数据。
线程工厂实际上是一个接口:
public interface ThreadFactory { Thread newThread(Runnable r); }
实际上就是使用工厂模式在创建线程的时候统一操作。
ThreadPoolExecutor具有ThreadFactory作为参数的构造函数。执行器创建新线程时使用此工厂。
使用ThreadFactory,可以自定义由executor创建的线程,以便它们具有正确的线程名、优先级、设置为daemon等。
import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; public class CustomThreadFactory implements ThreadFactory { private int counter; private String name; private List<String> stats; public CustomThreadFactory(String name) { counter = 1; this.name = name; stats = new ArrayList<String>(); } @Override public Thread newThread(Runnable runnable) { Thread t = new Thread(runnable, name + "-Thread_" + counter); t.setDaemon(true);//守護線程 t.setPriority(Thread.MAX_PRIORITY );//統一設置優先級 counter++; stats.add(String.format("Created thread %d with name %s on %s \n", t.getId(), t.getName(), new Date())); return t; } public String getStats() { StringBuffer buffer = new StringBuffer(); Iterator<String> it = stats.iterator(); while (it.hasNext()) { buffer.append(it.next()); } return buffer.toString(); } public static void main(String[] args) { CustomThreadFactory factory = new CustomThreadFactory("CustomThreadFactory"); Task task = new Task(); Thread thread; System.out.printf("Starting the Threads\n\n"); for (int i = 1; i <= 10; i++) { thread = factory.newThread(task); thread.start(); } System.out.printf("All Threads are created now\n\n"); System.out.printf("Give me CustomThreadFactory stats:\n\n" + factory.getStats()); } } class Task implements Runnable { @Override public void run() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程暂停
有3种暂停的方式
- 1、自己进入睡眠暂停;
- 2、使用CyclicBarrier,在某个阶段,多个线程统一暂停(await()),这里有介绍https://www.cnblogs.com/starcrm/p/12469364.html
- 3、使用Condition信号暂停;
第一种情况的例子:
try { Thread.sleep(2000); } catch (InterruptedException ex) { }
第三种情况的例子,通过主线程进行控制:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class PauseControl extends ReentrantLock { private static final long serialVersionUID = 176912639934052187L; // 线程暂挂标志 private volatile boolean suspended = false; private final Condition condSuspended = newCondition(); /** * 暂停线程 */ public void requestPause() { suspended = true; } /** * 恢复线程 */ public void proceed() { lock(); try { suspended = false; condSuspended.signalAll(); } finally { unlock(); } } /** * 当前线程仅在线程暂挂标记不为true的情况下才执行指定的目标动作。 * * @targetAction 目标动作 * @throws InterruptedException */ public void pauseIfNeccessary(Runnable targetAction) throws InterruptedException { lock(); try { while (suspended) { condSuspended.await(); } targetAction.run(); } finally { unlock(); } } } import java.util.Scanner; public class ThreadPauseDemo { final static PauseControl pc = new PauseControl(); public static void main(String[] args) { final Runnable action = new Runnable() { @Override public void run() { Debug.info("Master,I'm working..."); Tools.randomPause(300); } }; Thread slave = new Thread() { @Override public void run() { try { for (;;) { pc.pauseIfNeccessary(action); } } catch (InterruptedException e) { // 什么也不做 } } }; slave.setDaemon(true); slave.start(); askOnBehaveOfSlave(); } static void askOnBehaveOfSlave() { String answer; int minPause = 2000; try (Scanner sc = new Scanner(System.in)) { for (;;) { Tools.randomPause(8000, minPause); pc.requestPause(); Debug.info("Master,may I take a rest now?%n"); Debug.info("%n(1) OK,you may take a rest%n" + "(2) No, Keep working!%nPress any other key to quit:%n"); answer = sc.next(); if ("1".equals(answer)) { pc.requestPause(); Debug.info("Thank you,my master!"); minPause = 8000; } else if ("2".equals(answer)) { Debug.info("Yes,my master!"); pc.proceed(); minPause = 2000; } else { break; } }// for结束 }// try结束 Debug.info("Game over!"); } }
线程池
线程池是预初始化线程的集合。一般来说,集合的大小是固定的,但不是强制的。它有助于使用相同的线程执行N个任务。如果有比线程更多的任务,那么任务需要在队列结构(FIFO–先进先出)中等待。
当任何线程完成其执行时,它都可以从队列中获取新任务并执行它。所有任务完成后,线程将保持活动状态,并等待线程池中的更多任务。
线程池的意义在于:
1、减少在创建和销毁线程上所花的时间以及系统资源的开销,提升任务执行性能。
2、控制进程中线程数量的峰值,避免系统开销过大。
通过线程池,可创建一定数量的线程,并由线程池管理。在需要执行任务时,直接使用其中的线程。任务执行完成后,线程保留,并可用于执行下一个任务。如果任务比线程多,则等待线程空闲。
直接构建ThreadPoolExecutor
我们可以使用以下构造函数实例化ThreadPoolExecutor,其中ThreadFactory和RejectedExecutionHandler是可选参数,默认值分别是Executors.defaultThreadFactory()和ThreadPoolExecutor.AbortPolicy
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- int corePoolSize : 定义核心池大小。对于每个新请求,即使池中有空闲线程,也会根据核心池大小创建一个新线程
- int maximumPoolSize : 当发出请求并且核心池大小中的所有线程都忙时,将创建一个新线程,直到它达到最大池大小。在最大池大小之后,所有新请求都将进入队列。
- long keepAliveTime: 这是等待空闲线程死亡的时间。只有当线程计数大于核心池大小且小于或等于最大核心池大小时,空闲线程才会在keepAliveTime之后死亡。
- TimeUnit unit: keepAliveTime参数的时间单位
- BlockingQueue<Runnable> workQueue : BlockingQueue是一个队列,如果一个线程想要获取一个元素,并且队列是空的,那么线程将被阻塞,并等待元素在队列中可用。以同样的方式添加元素时,如果队列中没有空间,线程将被阻塞并等待获得可用空间。
- ThreadFactory threadFactory : 这是可选参数。传递用户定义的ThreadFactory
- RejectedExecutionHandler handler: 这是可选参数。在两种情况下,ThreadPoolExecutor.execute()可以拒绝新任务。
- 1、执行器已关闭。
- 2、超过工作队列容量和最大线程使用有限的界限,即它们是饱和时
- ThreadPoolExecutor.AbortPolicy:它中止任务并始终抛出RejectedExecutionException。
- ThreadPoolExecutor.CallerRunsPolicy:它自己(客户端)执行被拒绝的任务。
- ThreadPoolExecutor.DiscardPolicy:删除任务。
- ThreadPoolExecutor.DiscardOldestPolicy:当ThreadPoolExecutor.execute()由于工作队列的有限边界和最大限制而拒绝任务时,此策略只将任务放在工作队列的头。
默认策略是ThreadPoolExecutor.AbortPolicy。
例子
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolExecutorDemoOne { public static void main(final String[] args) throws Exception { final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 100, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy()); executor.execute(new BookReader("Ramayan")); executor.execute(new BookReader("Mahabharat")); executor.execute(new BookReader("Veda")); System.out.println("Old Max Pool Size:"+ executor.getMaximumPoolSize()); executor.setMaximumPoolSize(4); System.out.println("New Max Pool Size:"+ executor.getMaximumPoolSize()); executor.shutdown(); } } class BookReader implements Runnable { private String bookName; public BookReader(String bookName) { this.bookName = bookName; } @Override public void run() { for(int i = 0; i<5; i++) { System.out.println("Reading book: "+ bookName); try { Thread.sleep(200); } catch (InterruptedException ex) { System.out.println("I'm interrupted"); } } } }
使用Executors工厂方法创建ThreadPoolExecutor实例
可以使用Executors类提供的静态工厂方法来获取ThreadPoolExecutor,而不是直接使用上述构造函数之一创建ThreadPoolExecutor的实例。
- newCachedThreadPool()–创建一个线程池,该线程池根据需要创建新线程,提供无容量的阻塞队列 SynchronousQueue,因此任务提交之后,将会创建新的线程执行;线程空闲超过60s将会销毁 。
- newCachedThreadPool(ThreadFactory ThreadFactory)–创建一个线程池,该线程池根据需要创建新线程,但在可用时将重用先前构造的线程,并在需要时使用提供的ThreadFactory创建新线程。
- newFixedThreadPool(int nThreads)–创建一个线程池,该线程池重用在共享的无边界队列上操作的固定数量的线程。构造一个固定线程数目的线程池,配置的corePoolSize与maximumPoolSize大小相同,同时使用了一个无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在再阻塞队列,不会由RejectedExecutionHandler处理
- newFixedThreadPool(int nThreads,ThreadFactory ThreadFactory)–创建一个线程池,该线程池重用在共享的无边界队列上操作的固定数量的线程,在需要时使用提供的ThreadFactory创建新线程。
- newSingleThreadExecutor()–创建一个执行器,该执行器使用一个工作线程并在无限队列上操作,由一个线程串行执行任务。
- newSingleThreadExecutor(ThreadFactory ThreadFactory)–创建一个执行器,该执行器使用一个在无限队列上操作的工作线程,并在需要时使用提供的ThreadFactory创建一个新线程。
Executors的例子
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorExp { public static void main(String[] args) { ExecutorService executor = Executors.newSingleThreadExecutor(); for(int i = 0; i < 4; i++) { executor.execute(new VoidTask()); } executor.shutdown(); } } class VoidTask implements Runnable{ @Override public void run() { System.out.println("Executing task (thread name)- " + Thread.currentThread().getName()); // delay to keep the thread busy // so that pool is used try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
newFixedThreadPool.
此方法创建一个线程池,该线程池重用在共享的无界队列上操作的固定数量的线程。使用此方法时,内部Executors类使用以下参数创建ThreadPoolExecutor实例:
new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
实例
public class ExecutorExp { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); for(int i = 0; i < 4; i++) { executor.execute(new VoidTask()); } executor.shutdown(); } } class VoidTask implements Runnable{ @Override public void run() { System.out.println("Executing task (thread name)- " + Thread.currentThread().getName()); // delay to keep the thread busy // so that pool is used try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
newCachedThreadPool
该线程池根据需要创建新线程,但在以前构造的线程可用时将重用它们。使用此方法时,内部Executors类使用以下参数创建ThreadPoolExecutor实例
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
实例
public class ExecutorExp { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); for(int i = 0; i < 40; i++) { executor.execute(new VoidTask()); } executor.shutdown(); } } class VoidTask implements Runnable{ @Override public void run() { System.out.println("Executing task (thread name)- " + Thread.currentThread().getName()); // delay to keep the thread busy // so that pool is used try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //为了验证最大线程数Integer.MAX_VALUE,我们看一下结果,感觉这个 Executing task (thread name)- pool-1-thread-3 Executing task (thread name)- pool-1-thread-4 Executing task (thread name)- pool-1-thread-1 Executing task (thread name)- pool-1-thread-2 Executing task (thread name)- pool-1-thread-7 Executing task (thread name)- pool-1-thread-8 Executing task (thread name)- pool-1-thread-5 Executing task (thread name)- pool-1-thread-6 Executing task (thread name)- pool-1-thread-11 Executing task (thread name)- pool-1-thread-10 Executing task (thread name)- pool-1-thread-9 Executing task (thread name)- pool-1-thread-12 Executing task (thread name)- pool-1-thread-15 Executing task (thread name)- pool-1-thread-13 Executing task (thread name)- pool-1-thread-14 Executing task (thread name)- pool-1-thread-16 Executing task (thread name)- pool-1-thread-17 Executing task (thread name)- pool-1-thread-18 Executing task (thread name)- pool-1-thread-19 Executing task (thread name)- pool-1-thread-20 Executing task (thread name)- pool-1-thread-21 Executing task (thread name)- pool-1-thread-23 Executing task (thread name)- pool-1-thread-22 Executing task (thread name)- pool-1-thread-24 Executing task (thread name)- pool-1-thread-25 Executing task (thread name)- pool-1-thread-26 Executing task (thread name)- pool-1-thread-27 Executing task (thread name)- pool-1-thread-28 Executing task (thread name)- pool-1-thread-29 Executing task (thread name)- pool-1-thread-30 Executing task (thread name)- pool-1-thread-31 Executing task (thread name)- pool-1-thread-32 Executing task (thread name)- pool-1-thread-33 Executing task (thread name)- pool-1-thread-34 Executing task (thread name)- pool-1-thread-35 Executing task (thread name)- pool-1-thread-36 Executing task (thread name)- pool-1-thread-37 Executing task (thread name)- pool-1-thread-39 Executing task (thread name)- pool-1-thread-38 Executing task (thread name)- pool-1-thread-40
构造有定时功能的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); } public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, new DelayedWorkQueue(), threadFactory); }
实例
import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ScheduledThreadPoolExecutorExample { public static void main(String[] args) { ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(2); DelayTask task = new DelayTask("Repeat Task"); System.out.println("Created : " + task.getName()); executor.scheduleWithFixedDelay(task, 2, 2, TimeUnit.SECONDS); } } class DelayTask implements Runnable { private String name; public DelayTask(String name) { this.name = name; } public String getName() { return name; } public void run() { System.out.println("Executing : " + name + ", Current Seconds : " + new Date().getSeconds()); } }
Callable接口
Callable接口与Runnable接口相似,都用于定义线程的可执行任务。Callable在JDK 1.5引入,与Runnable相比,有三个优势:
1、可以在任务中抛出异常
2、可以终止任务
3、可以获取任务的返回值
这时候就会出现一个问题:新旧两种定义线程任务的方式,怎么前后兼容呢?
这时候就出现了FutureTask这个类
public class FutureTask<V> implements RunnableFuture<V> { // 封装实现Callable接口的任务 public FutureTask(Callable<V> callable) { //...} // 封装实现Runnable接口的任务,result是返回值,在任务完成后赋值(futureTask.get()会返回这个值)。 public FutureTask(Runnable runnable, V result) {//...} } public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }
使用FutureTask包装Runnable
import java.util.concurrent.Callable; public class MyRunnable implements Runnable { @Override public void run() { for(int i=0;i<3;i++){ System.out.println("num="+(i+1)); } } } import java.util.concurrent.FutureTask; public class Test { public static void main(String[] agrs){ String str = "default value"; FutureTask<String> task = new FutureTask<String>(new MyRunnable(),str); new Thread(task).start(); System.out.println(getFutureValue(task)); System.out.println("after task.get()!"); } public static String getFutureValue(FutureTask<String> task){ String str = "default value1"; try{ str = task.get(); }catch (Exception e){ System.out.println("task is canceled!"); } return str; } }
线程池管理线程任务
通过ExecutorService,可以使用4个方法执行线程任务。其中3个执行Runnable任务:
其中3个执行Runnable任务:
- void execute(Runnable command);
- < T > Future< T > submit(Runnable task, T result);
- Future< ? > submit(Runnable task);
1个执行Callable任务:
- < T > Future< T > submit(Callable< T > task); //执行Callable任务
只有通过submit方法执行的线程任务,才能获取到Future,才能通过Future管理线程。
示例代码如下:
import java.util.concurrent.Callable; public class MyCallable implements Callable<String> { @Override public String call() throws Exception{ int num=0; for(int i=0;i<3;i++){ num++; Thread.sleep(100); System.out.println("num="+num); } return String.valueOf(num); } } import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Test { public static void main(String[] agrs){ ExecutorService service = Executors.newFixedThreadPool(2); Future<String> future = service.submit(new MyCallable()); // 使用submit()执行Callable任务 System.out.println("执行结果:"+getFutureValue(future)); System.out.println("after task.get()!"); service.shutdown(); } public static String getFutureValue(Future<String> task){ String str = "default value"; try{ str = task.get(); }catch (Exception e){ System.out.println("task is canceled!"); } return str; } }
Executor和ExecutorService的区别
见图
Executors 是工厂helper类,包含几个用于为您创建预配置线程池实例的方法。这些类是一个很好的线程池起点。如果不需要应用任何自定义微调,请使用它。
Executor和ExecutorService接口用于在Java中处理不同的线程池实现。通常,您应该保持代码与线程池的实际实现分离,并在整个应用程序中使用这些接口。
Executor接口有一个execute方法来提交Runnable实例以供执行。
目前维护的开源产品:https://gitee.com/475660