(十七)java并发编程--任务执行之线程池的使用
来源:互联网 发布:淘宝护盾图片怎么授权 编辑:程序博客网 时间:2024/06/06 03:58
大多数并发程序围绕着”任务执行”来构造的: 任务通常是一些抽象的且离散的工作单元。通过把一个用程序的共工作分解到多个任务中,可以简化程序的组织结构,提供一种自然的事务边界来优化错误恢复过程,以及提供一种自然的工作结构来提升并发性。
- 在线程中执行任务
- 串行执行任务
- 1 显示的为任务创建线程
- 2 无限制创建线程的不足
- Executor框架
- 1基于Executor的web服务器
- 2执行策略
- 3 线程池
- 31 newFixedThreadPool
- 32 newCachedThreadPool
- 33 newSingleThreadExecutor
- 31 newScheduledThreadPool
- 4 Executor的生命周期
- 4 设置线程池的大小
- 5 管理队列任务
- 6任务饱和策略
- 携带结果的任务Callable和Future
1 在线程中执行任务
前面的博客中主要说的线程本身,但是我们如何管理和合理使用这些线程呢?
当围绕着“任务执行”来设计应用程序结构时,第一步要找到清晰的任务边界。在理想情况下,各个任务之间是互相独立的:任务并不依赖于其他任务的状态、结果或边界效应。
大多数服务器应用提供了一种自然的任务边界选择方式:以独立的请求为边界。WEb服务器、邮件服务器、文件服务器、EJB服务器以及数据库服务器等,这些服务器都能通过网络接受远程客户的连接请求。将独立的请求作为边界,既可以实现任务的独立性,又可以实现合理的任务规模。
2 串行执行任务
线程最简单的策略就是单个线程中串行地执行各项任务。如下,SingleThreadWebServer将会串行地处理它的任务。
package Executor;import java.net.ServerSocket;import java.net.Socket;/** * Created by fang on 2017/12/9. */public class SingleThreadWebServer { public static void main(String[] args) throws Exception { ServerSocket socket = new ServerSocket(80); while (true){ Socket connection = socket.accept(); handleRequest(connection); } } private static void handleRequest(Socket connection) { //... }}
主线程在接受连接与处理相关请求等操作之间不断的交替运行。当服务器正在处理请求时,新到来的连接必须等待直到处理完成,然后服务器
再次调用accept。
这里面,web请求处理中包含一组不同运算与io操作,服务其必须处理套接字(socket)IO以读取请求和写回响应,这些操作通常会由于网络阻塞或连通性问题被阻塞。此外服务器还可能处理文件IO或者数据库请求,这些操作同样会阻塞。
服务器的资源利用率非常低,因为单线程在等待IO操作完成时,cpu将处于空闲状态。
2.1 显示的为任务创建线程
那为每一个请求创建一个新的线程来提供服务,从而实现高响应性,如下ThreadPerTaskWebServer代码所示。
package Executor;import java.net.ServerSocket;import java.net.Socket;/** * Created by fang on 2017/12/9. * 创建多个线程. */public class ThreadPerTaskWebServer { public static void main(String[] args) throws Exception { ServerSocket socket = new ServerSocket(80); while (true){ final Socket connection = socket.accept(); Runnable task = new Runnable() { public void run() { handleRequest(connection); } } new Thread().start(); } } private static void handleRequest(Socket connection) { //... }}
对单线程的改造后,主线程将创建一个新线程来处理请求,而不是在主循环中进行。由此可得出如下结论:
1)任务处理过程从主线程中分离出来,可使主循环能够更快的等待下一个到来的连接。这使得程序在完成前面的请求之前可以接受新的请求,从而提高响应性。
2)任务可以并行处理,从而能同时服务多个请求。如果有多个处理器,或者任务由于某种原因被阻塞、例如等待IO完成、获取锁或者资源的可能性,程序的吞吐率将提高。
3)任务处理代码必须是线程安全的,当有多个任务时,会并发的调用这段代码。
“采用为每个任务开启一个线程”的方法能提升串行执行的性能。只要请求的到达速率不超出服务的请求处理能力,那么这种方法可以同时带来更快的响应性和吞吐率。
2.2 无限制创建线程的不足
如果如限制的创建线程还有如下问题
1)线程生命周期开销非常高。
2)资源消耗。
3)稳定性。
3 Executor框架
基于上述两种方式的问题(单线程和无线多线程的缺点),我们可以通过有界队列来防止高负荷的应用耗尽内存。而线程池则简化了线程的管理工作,jdk5中新增了java.util.concurrent 包提供了一种灵活线程池实现作为Executor框架的一部分。在java类中,任务的执行的主要抽象不是Thread,而是Executor,如下代码。
public interface Executor{ void execute(Runnable command);}
Executor 简单的接口却为异步任务执行框架提供了基础,该框架能支撑多种不同类型的任务执行策略。它提供了一种标准方法将任务的提交过程与执行过程解耦开来,并用Runnable来表示任务。Executor的实现还提供了生命周期的支持,以及统计信息收集、应用程序管理机制和性能监视机制。
Executor 基于一种生产者–消费者模式,提交任务的操作相当于生产者,执行任务的线程则相当于消费者。
3.1基于Executor的web服务器
基于Executor来构建web服务器是非常容易的。如下所示,定义了一个固定长度的线程池。
package Executor;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.Executor;import java.util.concurrent.Executors;/** * Created by fang on 2017/12/9. * 固定长度的线程池. */public class TaskExecutionWebServer { private static final int NTHREADS = 100; private static final Executor exec = Executors.newFixedThreadPool(NTHREADS); public static void main(String[] args) throws IOException{ ServerSocket serverSocket = new ServerSocket(); while (true){ final Socket connection = serverSocket.accept(); Runnable task = new Runnable() { public void run() { handleRequest(connection); } }; exec.execute(task); } } private static void handleRequest(Socket connection) { }}
通过使用Executor将请求处理任务的提交与任务的实际执行解耦开来。通常,Executor配置是一次性的,因此部署阶段可以完成,而提交任务的代码会不断的扩展整个程序中,增加了修改的难度。
可以为每个请求启动一个新线程的Executor。
public class ThreadPerTaskExecutor implements Executor{ public void execute(Runnable r){ new Thread(r).start(); }}
可以类似单线程的行为,以同步方式执行每个任务。
public class WithinThreadExecutor implements Executor{ pulic void execute(Runnable r){ r.run(); }}
以上三种方式只是作为基于Executor的web服务器的一个可能搭建情况。
3.2执行策略
将任务提交和执行解耦开来,可以为某任务指定和修改执行策略。在执行策略中包括了“what 、where、When、How”
等方面,包括:
1)在什么(what)线程中执行任务?
2)任务按照什么(what)顺序执行(FIFO、FIFO、优先级)?
3)有多少个(how many)任务能并发执行?
4)在队列中有多少个(how many)任务在等待执行?
5)如果系统由于过载而需要绝句一个任务,那么应该选择(which)哪个任务?另外,如何通(how)知应用程序有任务被拒绝?
6)在执行一个任务之前或之后,应该进行哪些(what)动作?
最佳策略取决于可用的计算机资源以及对服务质量的需求,通过限制并发数量可以确保程序不会因为资源耗尽而失败,或者由于在稀缺资源上发生竞争而严重影响性能。通过任务的提交与任务的执行分离开来,有助于在部署阶段选择与硬件资源最匹配的执行策略。
当我们看到下面这种形式的代码时:
new Thread(runnable).start()
并且你希望获得一种更灵活的执行策略时,请考虑使用Executor来代替Thread。
3.3 线程池
线程池,从字面意义上来看,是管理一组同构工作线程的资源池.
线程池之工作队列:保存了所有等待执行的任务。
线程池之工作者线程:从工作队列中获取一个线程,然后返回线程池并等待下一个任务。
java类库提供了灵活的线程池以及默认配置,可以通过调用Executors中的静态工厂方法之一来创建一个线程池。
3.3.1 newFixedThreadPool
固定线程池的长度,每提交一个任务就创建一个线程,直到达线程池的最大长度,这时线程池的规模将不会再变化(如果某个线程由于发生了未预期的Exception而结束那么线程池将会补充一个线程)。
代码如下:
package Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by fang on 2017/12/10. * 创建一个定长的线程池,可控制并发的最大数,超出线程会在队列中等待,代码如下. * 定长线程池的大小最好根据系统资源设置,如Runtime.getRuntime().availableProcessors()cpu处理器数. */public class ThreadPoolExecutorDemo2 { public static void main(String[] args) throws InterruptedException { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for(int i = 0;i<10;i++){ final int index = i; fixedThreadPool.execute(new Runnable() { public void run() { System.out.println(index); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } }}
3.3.2 newCachedThreadPool
创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,*那么将会回收空闲的线程,*而当需求增加时,则添加新的线程,线程的规模不存在
任何的限制。代码如下:
package Executor;import java.util.concurrent.Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by fang on 2017/12/10. * 是使用可缓存的线程池,如果需要处理的任务大于线程池的长度,可灵活回收线程, * 若无可回收,则建立新的线程. * * 因为线程池无限大,所以当执行第二个任务的时候第一个任务已经执行完成,会复用第一个任务的线程,而不用每次都新建线程. */public class ThreadPoolExecutorDemo1 { public static void main(String[] args) throws InterruptedException { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for(int i = 0;i<10;i++){ final int index = i; Thread.sleep(index*1000); cachedThreadPool.execute(new Runnable() { public void run() { System.out.println(index); } }); } }}
3.3.3 newSingleThreadExecutor
是一个单线程的Executor,它创建单个线程来执行任务,如果这个线程异常结束,会创建另外一个线程来替代。newSingleThreadExecutor能确保依照任务在队列中顺序
来串行执行(如FIFO、FIFO、优先级)。
package Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by fang on 2017/12/10. * 创建单线程化的线程池,只会用唯一的工作线程来执行,保证任务是按照(FIFO\LIFO,优先级来执行的) */public class ThreadPoolExecutorDemo4 { public static void main(String[] args) { ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for(int i=0;i<10;i++){ final int index = i; singleThreadExecutor.execute(new Runnable() { public void run() { System.out.println(index); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } }}
3.3.1 newScheduledThreadPool
newScheduledThreadPool创建了一个固定长度的线程池,而且以延迟或者定时的方式来执行任务,类似Timer。
“从单个线程串行执行”到“为每个任务分配一个线程”,变成基于线程池的策略,将对应用程序的稳定性产生重大的 影响:Web服务器不会再在高负载情况下执行失败。
由于服务器不会创建千万个线程来争夺有限的CPU和内存资源,因此服务器的性能将平缓的降低。
通过Executor,可以实现各种调优、管理、监视、记录日志、错误报告和其他功能,如果不使用任务的执行框架,增加这些功能是十分困难的。
package Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;/** * Created by fang on 2017/12/10. * 创建一个定长的线程池,支持定时以及周期性任务执行。代码如下。 * */public class ThreadPoolExecutorDemo3 { public static void main(String[] args) throws InterruptedException { //延期3s执行 ScheduledExecutorService scheduledThreadPool1 = Executors.newScheduledThreadPool(5); for(int i = 0;i<10;i++){ scheduledThreadPool1.schedule(new Runnable() { public void run() { System.out.println("delay 3 seconds"); } },3,TimeUnit.SECONDS); } //表示延迟1s后每3s执行一次. ScheduledExecutorService scheduledThreadPool2 = Executors.newScheduledThreadPool(5); scheduledThreadPool2.scheduleAtFixedRate(new Runnable() { public void run() { System.out.println("delay 1 seconds, and excute every 3 seconds"); } }, 1, 3, TimeUnit.SECONDS); }}
3.4 Executor的生命周期
可以创建一个Executor,但如何关闭Executor?Executor创建线程来执行任务。但JVM只有在所有(非守护)线程全部终止后才会退出(Executor所有线程自行完毕后退出)
因此,无法正确的关闭Executor,JVM则将无法结束。
Executor扩展了ExecutorService接口,添加了一些用于声明周期的管理办法(还有一些其他用于任务提交的便利方法),如下代码。
/** * Created by fang on 2017/12/10. * Executor 生命周期管理办法. */public interface ExecutorService { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination (long timeout, TimeUnit unit) throws InterruptedException; //.....一些其他用于任务提交的便利方法.}
shutdown():执行平缓的关闭过程,不再接受新任务,同时等待已经提交的任务执行完成(包括未开始执行的任务)。
shutdownNow():执行粗暴的关闭过程,尝试取消所有运行中的任务,并且不再启动队列中尚未开始的任务。
awaitTermination:等待ExecutorServer终止结束状态。通常在调用awaitTermination后会立即调用shutdown,从而产生同步的关闭ExecutorService效果。
isTerminated:轮询Executor是否已经结束。
如下代码所示。
package Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;/** * Created by fang on 2017/12/10. * 停止线程. */public class ExecutorServiceExample { public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(20); for(int i =0;i<100;i++){ executorService.submit(new NewTask()); } executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); }}class NewTask implements Runnable{ public void run() { }}
3.4 设置线程池的大小
看了《编发编程实战》中以及并发编程网中的部分,整理如下。
一般来说,大家认为线程池的大小经验值应该是这样设置:
(1)如果是CPU密集型,线程池的大小设置为N+1;
(2)如果是IO密集型,则线程池的大小设置为2N+1;
在IO优化中,这样的估算公式可能会更合适:
最佳线程数目 =( (线程等待时间 +线程CPU时间)/线程cpu时间) *cpu数目
显然,线程等待时间越长需要的线程数越多, 线程CPU时间所占比例越高, 需要线程越少. IO等待–>需要的线程多 CPU等待高–>需要的总线程会少
例如:
平均每个线程CPU时间0.5s, 而线程等待时间1.5s, CPU核数8, 根据公式得到((0.5+1.5)/0.5)*8 = 32
转化公式:
最佳线程数 = (线程等待时间与cpu时间比 + 1) * CPU数目
3.5 管理队列任务
用于保存等待执行的任务阻塞队列,可以选择以下阻塞队列。
ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,此队列按照(先进先出FIFO)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,也是按照FIFO原则对元素排序。吞吐量高于ArrayBlockingQueue,静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列,每个插入必须要等待到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量要高于Linked-BlokingQueue,静态工厂方法Executors.newCachedThreadpool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
3.6任务饱和策略
当有界队列被填满后,说明任务队列处于饱和状态,必须采用一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务
抛出异常。在jdk1.5中提供了4中饱和策略。
1. AbortPolicy:终止策略,直接抛出异常。
2. CallerRunsPolicy:只用调用者所在的线程来运行任务。
3. DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4. DiscardPolicy:不处理,丢弃掉。
4 携带结果的任务Callable和Future
之前的文章介绍过Callable和future,下面是callable future 和线程的方式来实现,如下代码。
package Executor;import java.util.ArrayList;import java.util.Date;import java.util.List;import java.util.concurrent.*;/** * Created by fang on 2017/12/10. * callable future ExecutorService example */public class MyCallable implements Callable<String>{ public String call() throws Exception { Thread.sleep(1000); return Thread.currentThread().getName(); } public static void main(String[] args) { //get ExecutorService form Executors unility class ,thread pool is10 ExecutorService executor = Executors.newFixedThreadPool(10); //create a list to hold the Future object associated with Callabel; List<Future<String>> futurelist = new ArrayList<Future<String>>(); //create MyCallable instance Callable<String> callable = new MyCallable(); for(int i=0;i<100;i++){ //submit Callable tasks to be executed by thread pool Future<String> future = executor.submit(callable); //add future to list ,we can get return value using future . futurelist.add(future); } for(Future<String> future:futurelist){ try { //print the return value of future ,notice the output delay in console //because Future.get() waits for task to get completed. System.out.println(new Date() + "::" + future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } //shut down the execuutor service now executor.shutdown(); }}
输出结果如下:
Sun Dec 10 16:48:25 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-10
从输出结果可以看到,线程池中建立了10个线程,线程池中的线程执行完毕就会释放,然后来了新的线程再继续创建。
小思:上篇中还在思考怎样效率最高,和录入自己的大脑中,今天有了一个办法,就是和其他人讨论问题,或者讲给其他人,你能给别人讲明白了,就是自己真的懂了。和别人在讨论过程中,就会更加深刻,第二点是把知识转化为图,大脑对图的印象要深刻与文字。
第三点带着问题去看书或者看视频,我可以看一下多线程相关的面试题,从书中找到答案,者需以上三种方式可以能提高些效率吧..
上篇大部分来自于《java并发编程》一是感觉这本书真的挺好的,二是,貌似可能“吃的不够多”,还不能自己消化了,可以吸收一些,
需要反复的反刍。
文中的一些搬运来自于:
https://www.journaldev.com/1090/java-callable-future-example
http://blog.csdn.net/hupitao/article/details/24453083
- (十七)java并发编程--任务执行之线程池的使用
- Java并发编程——线程池的使用(三)线程池执行任务、取消任务
- Java 并发编程之任务执行
- java并发 -线程池,任务执行
- Java并发编程之线程池任务监控
- java并发编程之线程执行器
- Java 并发编程之线程池的使用
- Java 并发编程之线程池的使用 (二)
- Java 并发编程之线程池的使用 (三)
- Java 并发编程之线程池的使用(一)
- Java 并发编程之线程池的使用 (二)
- Java 并发编程之线程池的使用 (三)
- Java并发编程之线程池的理解与使用
- java并发编程 之 线程池的使用
- Java并发编程:线程池的使用
- Java并发编程:线程池的使用
- Java并发编程:线程池的使用
- Java并发编程:线程池的使用
- 使用BeautifulSoup爬取CSDN博客文章
- ssm+redis的工具类
- 海量日志数据,提取出某日访问百度次数最多的那个IP。
- TensorFlow学习(一)sess.run
- 如何使用autoconf,automake,libtool自动生成Makefile文件
- (十七)java并发编程--任务执行之线程池的使用
- 《RabbitMQ实战指南》笔误及改进记录
- BZOJ1103(POI2007)[大都市meg]--DFS序+树状数组
- 一次数据库执行结果慢的梳理过程
- linux 下svn 忽略某类文件和目录
- 从键盘任意输入一个整数,编程判断它的奇偶性
- 练习(三)
- MyBatis框架核心之(三)注解使用接口映射
- Objective C 简历