Java线程池ExecutorService

2017-10-26  本文已影响0人  熊sir要早睡早起

之前一直都是听说xx框架内部使用线程池管理线程,优化的非常不错,然后本人对其的感觉就是嗯,没错,说的这么高大上肯定非常牛逼。然后理解到此结束,就没有然后了。

所以我们通常是怎么做的呢?

 new Thread(new Runnable() {
        
        @Override
        public void run() {
            // TODO Auto-generated method stub
            //doSomething
        }
    }).start();

感觉敲代码被人监视了,居然知道我是如何创建线程的,通常情况下,在安卓端我们的耗时操作,如网络请求,数据库操作等等会在子线程中执行,而我们一般也就是很轻松的直接new Thread就完事了,而在web开发中,服务器有时候接收处理请求,通常也会为一个请求创建一个子线程,但是当并发的请求数量非常多而每个线程执行的时间又很短的时候,这样频繁的创建和销毁线程会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。

简而言之,new Thread的弊端如下:

a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资        
     源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。

而使用线程池所带来的好处呢?

a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大的并发线程数,提高系统资源的使用率,同时避免多资源竞争,避    
    免阻塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能

接下来我们就慢慢了解一下如何使用线程池吧。

关于线程池

一:如何创建线程池。
Java通过Executors提供了四种线程池:

      newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需    
       要,可灵活回收空闲线程,如果没有可回收的线程,则会新建线程

     newFixedThreadPool 创建一个定常线程池,可控制线程最大并发数,超出的线程    
     会在队列中排队。

     newScheduledThread 创建一个定长线程池,支持定时及周期性任务‘

     newSingleThreadExecytor 创建一个单线程化的线程池,它只会用唯一的线程来    
     执行任务,保证所有任务按照指定顺序执行。

二:ExecutorService接口

ExecutorService接口继承自Executor接口,提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。增加了shutDown(),shutDownNow(),invokeAll(),invokeAny()和submit()等方法。如果需要支持即时关闭,也就是shutDownNow()方法,则任务需要正确处理中断。

线程池一共有五种状态, 分别是:

RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
进入TERMINATED的条件如下:
线程池不是RUNNING状态;
线程池状态不是TIDYING状态或TERMINATED状态;
如果线程池状态是SHUTDOWN并且workerQueue为空;
workerCount为0;
设置TIDYING状态成功。

三: ExecutorService 的submit() 与execute()区别
(1)接受的参数不一样。 submit可以接收runable和callable ; execute接收runnable
(2)submit有返回值,runnable没有返回值(假设我们有很多个做validation的task,我想等到所有的task执行完,然后每个task都告诉我执行结果是成功还是失败)
(3)submit方便Exception处理(针对的情况就是假设你的task会抛出checked或者unCheck的异常,你希望外面的调用者能够知晓这些异常并做出处理,那么就需要使用到submit捕获Future.get抛出的异常)

带返回值的Task

public class TaskResult implements Callable<String>{

private int id;

public TaskResult (int id){
    this.id = id;
}

/**
 * 任务的具体过程,一旦任务转给ExecutorService的submit方法,
 * 该方法自动在一个线程上执行
 */
@Override
public String call() throws Exception {
    // TODO Auto-generated method stub
       System.out.println("doSomething!!!"+ Thread.currentThread().getName());
       if (new Random().nextBoolean()) 
           throw new Exception("出错了"+Thread.currentThread().getName());

    
        return "call()方法被自动调用,任务的结果是:" + id + "    " + Thread.currentThread().getName();  

}
}

Main方法中submit:

public static void main(String[] args) {
    
    //创建一个可换成的线程池
    ExecutorService executorService = Executors.newCachedThreadPool();
    List<Future<String>> result = new ArrayList<Future<String>>();
    
    for (int i = 0; i <10; i++) {
        
        Future<String> future = executorService.submit(new TaskResult(i));
        
        result.add(future);
        
    }
    executorService.shutdown();
    
     for (Future<String> fs : result) {  
            try {  
                System.out.println(fs.get()); // 打印各个线程(任务)执行的结果  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            } catch (ExecutionException e) {  
                executorService.shutdownNow();  
                e.printStackTrace();  
                return;  
            }  
        }  
}

代码逻辑为循环创建十个任务并执行,使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中 。并随机模拟出错的task,最后使用shutdown关闭ExecutorService,我们观察输出结果:

  doSomething!!!pool-1-thread-1
  doSomething!!!pool-1-thread-3
  call()方法被自动调用,任务的结果是:1    pool-1-thread-1
  doSomething!!!pool-1-thread-4
  doSomething!!!pool-1-thread-2
  doSomething!!!pool-1-thread-5
  doSomething!!!pool-1-thread-6
  doSomething!!!pool-1-thread-7
  doSomething!!!pool-1-thread-8
  java.util.concurrent.ExecutionException: java.lang.Exception: 出错了pool-1-thread-2
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:188)
at 线程池Executor.ExecutorDemo.main(ExecutorDemo.java:33)
  Caused by: java.lang.Exception: 出错了pool-1-thread-2
at 线程池Executor.TaskResult.call(TaskResult.java:23)
at 线程池Executor.TaskResult.call(TaskResult.java:1)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at       
   java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at   
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
doSomething!!!pool-1-thread-9
doSomething!!!pool-1-thread-10

十个线程都创建了,然后随机出错的是第二个,可以发现只要task出错,,其它的task就停止执行。
shutdown() 方法在终止前允许执行以前提交的任务,
shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。关闭未使用的 ExecutorService 以允许回收其资源。
一般分两个阶段关闭 ExecutorService。第一阶段调用 shutdown 拒绝传入任务,然后调用 shutdownNow(如有必要)取消所有遗留的任务

至于另外一种runnable方法,就不做介绍了,毕竟通常情况下我们都是使用runable接口来实现线程的,它调用的是run方法,callable调用的是call方法,携带一个泛型返回值。runnable接口实现的没有返回值的并发编程

接下来我们通过代码来体验一下不用线程池和使用四种方式实现线程池的各种差异吧。

(1)不适用线程池

for (int i = 0; i < 10; i++) {

        final int index = i;

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out
                        .println(index+"==============" + Thread.currentThread().getName());
            }
        }).start();

    }

看看执行结果:好吧,不出意外正常时创建了十个线程

0==============Thread-0
1==============Thread-1
2==============Thread-2
3==============Thread-3
4==============Thread-4
5==============Thread-5
6==============Thread-6
7==============Thread-7
8==============Thread-8
9==============Thread-9

(2). newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

  ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

    for (int i = 0; i < 10; i++) {

        final int index = i;

        try {

            Thread.sleep( 300);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        cachedThreadPool.execute(new Runnable() {

            @Override
            public void run() {

                System.out.println(index+"=============="+Thread.currentThread().getName());

            }

        });

    }

查看一下执行结果:不出意外当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

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==============pool-1-thread-1

(3)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,所以每次休眠一秒打印三条数据,通常情况下我们线程池的大小一般会根据系统资源进行设置,如Runtime.getRuntime().availableProcessors()。

(3) newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

ScheduledExecutorService scheduledThreadPool =     
 Executors
            .newScheduledThreadPool(5);

    scheduledThreadPool.schedule(new Runnable() {

        @Override
        public void run() {

            System.out.println("delay 2 seconds");

        }

    }, 2, TimeUnit.SECONDS);
}

运行程序可以在控制台发现2s后才打印出数据。

定期执行示例代码如下:

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override

public void run() {

    System.out.println("delay 1 seconds, and excute every 2 seconds");

}}, 1,2, TimeUnit.SECONDS);

表示延迟1秒后每2秒执行一次。ScheduledExecutorService比Timer更安全,功能更强大

(4)、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(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.currentThread().getName());

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }

            }

        });

    }

观察控制台输出结果:

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=============pool-1-thread-1

结果依次输出,相当于顺序执行各个任务。
现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。

好了,到此就告一段落,谢谢大家。

参考引用:  
http://blog.csdn.net/nk_tf/article/details/51959276
http://blog.csdn.net/chenaini119/article/details/51849222
http://www.cnblogs.com/Steven0805/p/6393443.html

上一篇下一篇

猜你喜欢

热点阅读