Java5线程并发库之线程池EXECUTORS

线程池Executors

接下来我们来介绍一下线程池。Java5中非常显著的一个功能就叫做线程池。

线程池有什么好处呢?

线程池有什么好处呢?假如我们自己想做一个tomcat这样的服务器,非常难。难在于我们的性能升不上去。假设我们写了一个tomcat服务器程序,每一个客户端来了是不是连接我们,连接我们,连上我们以后,我们就要启动一个线程,这个线程就跟它单独对话,为什么要新起一个线程,而不是直接在这个服务器程序上跟这个客户端对话呢?这样,如果我跟这个客户端唠唠叨叨的不停的对话,别的客户要连的时候,我这个程序是不是一直堵在那个地方,一直在跟第一个客户端对话呀。所以我们应该启动多线程,每一个客户来了呢,我们用一个线程去接待它。生活中也有这样的场景,比如说我要办喜事,大家呢都来祝贺,为了表示礼仪呢,我都要一个个接待,我接待到一个朋友是不是就寒暄两句就可以了,然后交给接待人员招呼,不可以一直拉着一个在那里不停的唠,这样旁边等在那里的是不是就非常不耐烦了。所以说,为了让大家来了以后都可以非常迅速的接待大家,所以,我一旦接进来我就创建一个线程,让那个线程再单独的为他提供服务,我不能继续为他提供服务了,还有别人要等待我。等待我去招待它们。所以说是这样的。好现在启动一个线程,这个线程跟他服务一会儿了呢,是不是就聊完了呀,话说完了,我们写的程序就是new Thread(){run(){}}.start();Thread里面重写的run方法运行的代码就是接待他,聊天聊一会儿。执行完了,这个run方法就结束了,run方法一结束,这个线程也就结束了。比如现在有60个客人要接待,我这样写代码的话,在我背后,我要请60个接待人员,每一个接待人员都是一次性的,接待完一个客户以后就报销。但是,我希望的是一个接待人员接待完一个客人安顿好了以后,他还可以继续接待下一个客人,这怎么办呢?那这样的话,这个程序就很难写,比如说当中某个接待人员他已经接待完了一个客户,那么这时候他要去问,这时候还有没有新的客户需要他来接待。那么run方法里面就要写代码了,聊完了以后要查询某个池子中是否还有客户要接待。如果还有客户要接待的话,就直接把它取出来,就接着跟他聊。跟一个客户聊完了之后,就循环回来看池子里面还有没有客户,再循环完了,我问池子里面还有没有客户,如果没有了呢?怎么办?这个线程要等待,它什么时候自己醒来呢?当池子里面有了新客户的时候通知他,它再醒来,接着去接待。这是不是我们写起来,虽然我们现在感觉我们能写,但还是有点复杂呀!

那么,这个东西我们明白原理就好,不需要我们去做,现在java5已经为我们提供了线程池的api.我们直接使用它的线程池就可以解决我们上面说的那个问题。

固定线程数的线程池Executors.newFixedThreadPool(3)

现在就用代码演示一下Executors(执行者集合)线程池;

线程池,就是有多个线程,这个线程它一看到有任务,它就去拿过来执行,执行完了,这个线程不结束,它又去拿,没拿到,他就等待。

public class ThreadPoolTest {

    /**

     * @param args

     */

    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newFixedThreadPool(3);// 创建一个可重用固定线程数的线程池

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

            final int task = i;

            threadPool.execute(new Runnable() {

                @Override

                public void run() {

                    for (int j = 1; j <= 10; j++) {

                        try {

                            Thread.sleep(20);

                        } catch (InterruptedException e) {

                            // TODO Auto-generated catch block

                            e.printStackTrace();

                        }

                        System.out.println(Thread.currentThread().getName() + " is looping of " + j

                                + " for  task of " + task);

                    }

                }

            });

        }

        System.out.println("all of 10 tasks have committed! ");

    }

}

从以上代码的输出,我们可以清晰地看到,虽然我们循环放进了10个任务给线程池执行,但是每次只有3个任务被执行了,执行完了3个任务之后,再执行下3个任务。因为我总共只创建了3个线程在线程池里面。池子里里面有3个线程,我可以往池子里面扔N个任务,然后这些线程尽最大所能的去为这些任务服务,但是,同时被服务的只有3个任务,其它的哪些任务在排队等待。这个线程池只能三个一批的执行。最后当输出完毕,我们看到这个程序并没有结束,因为线程池里一直有三个线程,这三个线程是不会结束的,线程不死,程序就不会结束。如果你想让程序运行完了结束,你可以在程序最后调用threadPool.shutdown ();方法。这个方法就是说没事干了,池子里面没有任何任务要执行了,大家都空闲下来了,既然都歇着了,那就把所有的线程也就干掉吧。关闭线程池。如果调用的是threadPool.shutdownNow();方法,那就是说,不管任务做没做完,运行到这行代码,就把线程池里面的线程都干掉,关闭线程池。线程池里面就两个东西,第一个有几个线程。第二个,往线程池里面扔了多少个任务。我们不用说把任务交给具体哪个线程去运行,我们只要把任务丢到这个线程池里面去,就没我事了,线程池自己看着办。我把任务交给你线程池,你线程池看你有没有空闲的线程你就去运行,没有你就让它等着。这就是线程池。我们以上代码用的是固定线程数的线程池。

缓存的线程池Executors.newCachedThreadPool()

接下来我们介绍,缓存的线程池。缓存的线程池就是说,当你的任务来了,我服务不过来了,我就自动增加新的线程。他内部的线程个数是不确定的。比如,当刚开始只有3个任务,他就创建3个线程在那里运行,结果突然来了6个任务,我就增加6个线程去服务。当运行一段时间之后,又变成只有一个任务了,其他线程给空闲下来了,它超时一段时间以后,过一段时间以后,他又把那些空闲出来的线程给收回去。不浪费嘛。以下是代码:

public class ThreadPoolTest {

    /**

     * @param args

     */

    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newCachedThreadPool();

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

            final int task = i;

            threadPool.execute(new Runnable() {

                @Override

                public void run() {

                    for (int j = 1; j <= 10; j++) {

                        try {

                            Thread.sleep(20);

                        } catch (InterruptedException e) {

                            // TODO Auto-generated catch block

                            e.printStackTrace();

                        }

                        System.out.println(Thread.currentThread().getName() + " is looping of " + j

                                + " for  task of " + task);

                    }

                }

            });

        }

        System.out.println("all of 10 tasks have committed! ");

        // threadPool.shutdownNow();

    }

}

通过打印结果,我们可以看到,我们循环放进去的10个任务,一开始就都得到了执行,因为缓存线程池同时扩充了10个线程,每一个线程包一个任务回去执行。这就是,这个线程池里面的线程数是动态变化的。它运行完了以后,程序也是不会结束的,因为它还要维持线程,等待新的任务。

线程池里面只有一个线程newSingleThreadExecutor()

接下来,还有一种特殊的线程池,这个线程池里面只有一个线程,static ExecutorService newSingleThreadExecutor(),那就跟我们的单个线程一样了,没有什么池子的概念了。但是他的好处好在哪里呢,如果这个池子里面的线程死了,它就会自动再找一个替补,再搞出一个线程来,反正保证要有一个线程。比如,我们要实现线程死了以后,把它再重新启动。这是网上很多人问的问题,用这个api,搞个单线程池,如果你里面的线程死了,它马上给你恢复一个来。至于它怎么搞的,我就不管了。反正它能够完成你的需求。如果非说要把原来死掉的那个线程恢复的话,这句话本来就有错误,这家伙都已经死了,顶多说搞一个接班人,不可能让死了的复活的。

定时器的线程池Executors.newScheduledThreadPool(3)

线程池里面还有一个线程池,就是叫定时器的线程池,用线程池启动定时器。

Executors.newScheduledThreadPool(3).schedule(new Runnable() {

            @Override

            public void run() {

                System.out.println("bombing!");

            }

        }, 10, TimeUnit.SECONDS);

以上代码指定在程序运行后10秒,执行Runnable,打印bombing!

如果我想在程序运行6秒以后执行Runnable,并且以后每隔2秒的频率,不停的执行Runnable,那也可以,看下面的实现。

首先调用Executors的静态方法static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 方法创建一个固定大小的线程池,这个线程池可以调用ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 方法,这个方法可以创建并执行一个定时任务,也就是创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。

Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable() {

            @Override

            public void run() {

                System.out.println("bombing!");

            }

        }, 6, 2, TimeUnit.SECONDS);

首先是创建了一个调度线程池,初始化了3个线程。然后调度任务,指定调度任务的时间规则。

这个定时器稍微有一个瑕疵,schedule用的总是相对时间,就是多少秒以后,可是有时候我们要定在今天晚上凌晨3点,这样的指定时间。他没有这种方法,没有直接指定Date的方法,你可以用date.getTime() - System.currentTimeMillis(),就是把你要设定的时间,减去系统当前时间,就可以得出要过多长时间以后执行。

猜你喜欢

转载自my.oschina.net/u/3512041/blog/1822029
今日推荐