多线程一般有两种实现方式一个是继承thread类,另外一个实现runnable接口,在项目实践中以实现runnable为主
主要是因为
1.可以避免由于Java的单一继承带来的局限性
2.增强程序健壮性,代码能够被多个线程共享,代码与数据独立
3.适合多个相同程序代码的线程区处理同一资源情况
Executor 框架与线程池
Executor 框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable 等。
executor接口中定义了一个方法execute,该方法接受一个runnable实例,用它来执行一个任务,任务即一个实现了runnable接口的类,executorservice接口继承自executor接口,提供了更加丰富的多线程实现方法。
executorservice生命周期包括三种状态:运行,关闭,终止。创建后面便进入运行,调用了shutdown(),便进入关闭状态,但是不再接收新的任务,当任务执行完成进入终止状态。
executor提供了四种方法用于创建线程池
ExecutorService newFixedThreadPool(int nThreads)
创建固定数量的线程池。
使用前会查看以前有没有建立线程,如果有,那么就用以前的线程,如果没有,那么建立新的线程,但是不能随时随地建立。
任意时间点,只有固定数量的线程活动,如果此时需要建立新的线程,那么需要放入等待队列,直到当前线程中的某个线程终止直到移除池子
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute将重用以前构造的线程,如果没有线程可用,则创建一个新的线程并且添加到线程池。终止并且从缓存中移除已有60s未使用的线程。
缓存池子,使用前会查看池子中有没有以前建立的线程,如果有,那么就用以前的线程,如果没有,那么建立一个新的线程加入。
缓存型池子通常用于执行一些生存周期很短的异步任务。对于生存周期比较短的异步任务,首选
能重新启用的线程必须是timeout IDLE内池中的线程,缺省timeout是60s,超过这个idle时长,线程实例会被终止并且移除池子
public static ExecutorService newSingleThreadExecutor()
创建一个单一线程的线程池
任意时间点只有一个线程的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时以及周期性的任务执行的线程池
调度型的线程池
实例
public class TestCachedThreadPool{
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++){
executorService.execute(new TestRunnable());
System.out.println("************* a" + i + " *************");
}
executorService.shutdown();
}
}
class TestRunnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName() + "线程被调用了。");
}
}
callable
runnable是没有返回值,callable是有返回值,并且 Callable 的 call()方法只能通过 ExecutorService 的 submit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future。
Callable<ImageInfo> call = new Callable<ImageInfo>() {
public ImageInfo call() throws Exception {
logger.info("");
return imageforApkService.queryDataByToken(tokenid1);
}
};
声明线程池
public static <T> T execute(Callable<T> task, int seconds) throws Exception{
ExecutorService threadPool=Executors.newCachedThreadPool();
try{
Future<T> future=threadPool.submit(task);
return future.get(seconds,TimeUnit.MILLISECONDS);
}
catch(Exception e){
e.printStackTrace();
throw new Exception();
}
finally{
threadPool.shutdownNow();
}
}
调用
im = TimeOutUtils.execute(call, imgTimeout);
这个不仅返回结果,并且还能在规定时间内执行,如果不能获取结果则抛出异常
自定义线程池
//创建等待队列
BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);
//创建线程池,池中保存的线程数为3,允许的最大线程数为5
ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue);
主要方法讲解
public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)
corePoolSize
线程池中所有保存核心线程数,包括空闲线程、
maximumPoolSize
线程池中允许最大的线程数
keepAliveTime
线程池中空闲线程所能持续最长的时间
unit
空闲时间单位
workQueue
任务执行前保存任务的队列,即等待队列
当试图通过 excute 方法讲一个 Runnable 任务添加到线程池中时,按照如下顺序来处理:
-
如果线程池中的线程数量少于 corePoolSize,即使线程池中有空闲线程,也会创建一个新的线程来执行新添加的任务;
-
如果线程池中的线程数量大于等于 corePoolSize,但缓冲队列 workQueue 未满,则将新添加的任务放到 workQueue 中,按照 FIFO 的原则依次等待执行(线程池中有线程空闲出来后依次将缓冲队列中的任务交付给空闲的线程执行);
-
如果线程池中的线程数量大于等于 corePoolSize,且缓冲队列 workQueue 已满,但线程池中的线程数量小于 maximumPoolSize,则会创建新的线程来处理被添加的任务;
- 如果线程池中的线程数量等于了 maximumPoolSize,有 4 种处理方式。
排队策略
-
直接提交。缓冲队列采用 SynchronousQueue,它将任务直接交给线程处理而不保持它们。如果不存在可用于立即运行任务的线程(即线程池中的线程都在工作),则试图把任务加入缓冲队列将会失败,因此会构造一个新的线程来处理新添加的任务,并将其加入到线程池中。直接提交通常要求无界 maximumPoolSizes(Integer.MAX_VALUE) 以避免拒绝新提交的任务。newCachedThreadPool 采用的便是这种策略。
-
无界队列。使用无界队列(典型的便是采用预定义容量的 LinkedBlockingQueue,理论上是该缓冲队列可以对无限多的任务排队)将导致在所有 corePoolSize 线程都工作的情况下将新任务加入到缓冲队列中。这样,创建的线程就不会超过 corePoolSize,也因此,maximumPoolSize 的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列。newFixedThreadPool采用的便是这种策略。
- 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(一般缓冲队列使用 ArrayBlockingQueue,并制定队列的最大长度)有助于防止资源耗尽,但是可能较难调整和控制,队列大小和最大池大小需要相互折衷,需要设定合理的参数。