线程池ExecutorService 一. new Thread的弊端
执行一个异步任务你还只是如下new Thread吗?
new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start();
那你就太out了,new Thread的弊端如下:
每次new Thread新建对象性能差.
线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom.
缺乏更多功能,如定时执行、定期执行、线程中断.
相比new Thread,Java提供的四种线程池的好处在于:
重用存在的线程,减少对象创建、消亡的开销,性能佳.
可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞.
提供定时执行、定期执行、单线程、并发数控制等功能.
二. ExecutorService详解Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待.
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行.
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行.
01. submit()与execute()区别接收的参数不一样 submit()可以接受runnable和callable 有返回值execute()接受runnable 无返回值
submit有返回值,而execute没有
Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion.
用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么.
submit方便Exception处理
There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will Go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.
意思就是如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常.
import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ExecutorServiceTest { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); List> resultList = new ArrayList >(); // 创建10个任务并执行 for (int i = 0; i < 10; i++) { // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中 Future future = executorService.submit(new TaskWithResult(i)); // 将任务执行结果存储到List中 resultList.add(future); } executorService.shutdown(); // 遍历任务的结果 for (Future fs : resultList) { try { System.out.println(fs.get()); // 打印各个线程(任务)执行的结果 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { executorService.shutdownNow(); e.printStackTrace(); return; } } } } class TaskWithResult implements Callable { private int id; public TaskWithResult(int id) { this.id = id; } /** * 任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行. * * @return * @throws Exception */ public String call() throws Exception { System.out.println("call()方法被自动调用,干活!!! " + Thread.currentThread().getName()); if (new Random().nextBoolean()) throw new TaskException("Meet error in task." + Thread.currentThread().getName()); // 一个模拟耗时的操作 for (int i = 999999999; i > 0; i--) ; return "call()方法被自动调用,任务的结果是:" + id + " " + Thread.currentThread().getName(); } } class TaskException extends Exception { public TaskException(String message) { super(message); } }
执行的结果类似于:
call()方法被自动调用,干活!!! pool-1-thread-1 call()方法被自动调用,干活!!! pool-1-thread-2 call()方法被自动调用,干活!!! pool-1-thread-3 call()方法被自动调用,干活!!! pool-1-thread-5 call()方法被自动调用,干活!!! pool-1-thread-7 call()方法被自动调用,干活!!! pool-1-thread-4 call()方法被自动调用,干活!!! pool-1-thread-6 call()方法被自动调用,干活!!! pool-1-thread-7 call()方法被自动调用,干活!!! pool-1-thread-5 call()方法被自动调用,干活!!! pool-1-thread-8 call()方法被自动调用,任务的结果是:0 pool-1-thread-1 call()方法被自动调用,任务的结果是:1 pool-1-thread-2 java.util.concurrent.ExecutionException: com.cicc.pts.TaskException: Meet error in task.pool-1-thread-3 at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:222) at java.util.concurrent.FutureTask.get(FutureTask.java:83) at com.cicc.pts.ExecutorServiceTest.main(ExecutorServiceTest.java:29) Caused by: com.cicc.pts.TaskException: Meet error in task.pool-1-thread-3 at com.cicc.pts.TaskWithResult.call(ExecutorServiceTest.java:57) at com.cicc.pts.TaskWithResult.call(ExecutorServiceTest.java:1) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:619)
可以看见一旦某个task出错,其它的task就停止执行.
可以关闭 ExecutorService,这将导致其拒绝新任务.提供两个方法来关闭 ExecutorService.
shutdown() 方法在终止前允许执行以前提交的任务,
shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务.在终止时执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务.关闭未使用的 ExecutorService 以允许回收其资源.
一般分两个阶段关闭 ExecutorService.第一阶段调用 shutdown 拒绝传入任务,然后调用 shutdownNow(如有必要)取消所有遗留的任务
// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务.
threadPool.shutdown();
03. Runnable()与Callable()区别
如果是一个多线程协作程序,比如菲波拉切数列,1,1,2,3,5,8…使用多线程来计算.
但后者需要前者的结果,就需要用callable接口了.
callable用法和runnable一样,只不过调用的是call方法,该方法有一个泛型返回值类型,你可以任意指定.
runnable接口实现的没有返回值的并发编程.
callable实现的存在返回值的并发编程.(call的返回值String受泛型的影响) 使用Future获取返回值.
这里写图片描述
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.示例代码如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; try { Thread.sleep(index * 1000); } catch (InterruptedException e) { e.printStackTrace(); } cachedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(index); } }); }
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程.
02. newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待.示例代码如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; fixedThreadPool.execute(new Runnable() { @Override public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); }
因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字.
定长线程池的大小最好根据系统资源进行设置.如Runtime.getRuntime().availableProcessors().可参考PreloadDataCache.
创建一个定长线程池,支持定时及周期性任务执行.延迟执行示例代码如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.out.println("delay 3 seconds"); } }, 3, TimeUnit.SECONDS);
表示延迟3秒执行.
定期执行示例代码如下:
scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("delay 1 seconds, and excute every 3 seconds"); } }, 1, 3, TimeUnit.SECONDS);
表示延迟1秒后每3秒执行一次.
ScheduledExecutorService比Timer更安全,功能更强大,后面会有一篇多带带进行对比.
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行.示例代码如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; singleThreadExecutor.execute(new Runnable() { @Override public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); }
结果依次输出,相当于顺序执行各个任务.
现行大多数GUI程序都是单线程的.Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作.
总结:
使用ExecutorService的submit函数由于execute函数
异常如何处理,异常后其他task停止
四. 线程安全问题 01. 日期类型转换SimpleDateFormat来做Date到String的类型转换,建议使用Apache commons-lang中的FastDateFormat。
因为JDK里自带的SimpleDateFormat存在线程不安全问题。
maven依赖:
commons-lang commons-lang 2.5
代码:
private String initDate() { Date d = new Date(); FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); return fdf.format(d); }
多线程问题,主要是多线程执行时的顺序是随机的,无法保证同一代码的执行顺序,任意两步代码(非原子)操作都存在安全问题
02. 线程安全类型多线程问题,主要是多线程执行时的顺序是随机的,无法保证同一代码的执行顺序,任意两步代码(非原子)操作都存在安全问题(01). 锁在String
String str="a"; synchronized(str){ str = "b"; //str变为一个新对象,锁失效,字符的赋值是新new一个String然后赋值的 }(02). AtomicInteger
i++,使用java.util.concurrent.atomic下的原子类代替来做多线程的计数器
i++是两步,读取i的变量的值,然后更新+1,所以不安全, 使用AtomicInteger
HashMap,ArrayList,使用ConcurrentHashMap,CopyOnWriteArrayList代替
(04). StringBufferStringBuilder,使用StringBuffer代替
参考引用:
https://www.cnblogs.com/Steve...
https://blog.csdn.net/insistg...
https://blog.csdn.net/c511362...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/73222.html
摘要:线程池常见实现线程池一般包含三个主要部分调度器决定由哪个线程来执行任务执行任务所能够的最大耗时等线程队列存放并管理着一系列线程这些线程都处于阻塞状态或休眠状态任务队列存放着用户提交的需要被执行的任务一般任务的执行的即先提交的任务先被执行调度 线程池常见实现 线程池一般包含三个主要部分: 调度器: 决定由哪个线程来执行任务, 执行任务所能够的最大耗时等 线程队列: 存放并管理着一系列线...
摘要:并发包参考多线程的同步协助同步控制扩展功能重入锁之前重入锁性能好于但开始优化现在二者的性能相差不大。倒计时器的扩展循栅栏。做好异常处理工作。线程池的内部实现该部分待看书 JDK 并发包 参考:> https://github.com/chengbingh... 3.1 多线程的同步协助:同步控制 3.1.1 synchronized 扩展功能:重入锁jdk1.5之前重入锁Reentra...
摘要:并发编程实战水平很高,然而并不是本好书。一是多线程的控制,二是并发同步的管理。最后,使用和来关闭线程池,停止其中的线程。当线程调用或等阻塞时,对这个线程调用会使线程醒来,并受到,且线程的中断标记被设置。 《Java并发编程实战》水平很高,然而并不是本好书。组织混乱、长篇大论、难以消化,中文翻译也较死板。这里是一篇批评此书的帖子,很是贴切。俗话说:看到有这么多人骂你,我就放心了。 然而知...
摘要:本文主要内容为简单总结中线程池的相关信息。方法簇方法簇用于创建固定线程数的线程池。三种常见线程池的对比上文总结了工具类创建常见线程池的方法,现对三种线程池区别进行比较。 概述 线程可认为是操作系统可调度的最小的程序执行序列,一般作为进程的组成部分,同一进程中多个线程可共享该进程的资源(如内存等)。在单核处理器架构下,操作系统一般使用分时的方式实现多线程;在多核处理器架构下,多个线程能够...
摘要:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。创建一个定长线程池,支持定时及周期性任务执行。 ExecutorService是Java中对线程池定义的一个接口,它java.util.concurrent包中. 创建一个什么样的ExecutorService的实例(即线程池)需要g根据具体应用场景而定,不过Java给我们提供了一个Executors工厂类,它可以帮助...
阅读 1831·2021-11-11 16:55
阅读 751·2019-08-30 15:53
阅读 3591·2019-08-30 15:45
阅读 672·2019-08-30 14:10
阅读 3264·2019-08-30 12:46
阅读 2124·2019-08-29 13:15
阅读 2026·2019-08-26 13:48
阅读 935·2019-08-26 12:23