小解——Java线程池的使用

前言:本文将讲解Java线程池的一些用法,涉及到的接口,类(Executor,Executors,ExecutorService),如何通过线程池管理线程,使我们的程序更加高效。

首先,写一个实现Runnable的类

 

public class ListOff implements Runnable{

    protected int countDown = 5;
    private static int taskCount = 0;
    private final int id = taskCount++;
    
    public ListOff(){}
    public ListOff(int countDown){
        this.countDown = countDown;
    }
    
    public String status() {
        return "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!") + ").";
    }
    
    @Override
    public void run() {
        while (countDown-- > 0) {
            System.out.println(status());
            Thread.yield();
        }
    }
}


 

代码很简单,主要是让该线程每次打印一下当前countDown的值,为稍后执行多个线程的时候作对比,显示线程之间是如何工作的。


这是一个线程,假如我们不使用线程池管理,那么,当我们有多个线程的时候,是这样的:


—————忧郁又带着些调皮的分割线(使用多线程)——————

 

public class BasicThreads {

    public static void main(String[] args) {
        
        Thread t = new Thread(new ListOff());
        Thread t1 = new Thread(new ListOff());
        Thread t2 = new Thread(new ListOff());
        t.start();
        t1.start();
        t2.start();
        System.out.println("Waiting for ListOff!");
    }
}


执行结果:

 

Waiting for ListOff!
#0(4).
#2(4).
#1(4).
#2(3).
#1(3).
#2(2).
#1(2).
#2(1).
#1(1).
#2(Liftoff!).
#1(Liftoff!).
#0(3).
#0(2).
#0(1).
#0(Liftoff!).


 

分析:没啥好分析,错综繁乱的结果,这里解释一下为什么“Waiting for ListOff!”会在最前方,因为Main方法本身就是一个线程。所有他和其他线程并排执行,可先可后。而且,他们的执行顺序不是我们认为可以控制的,而是由Java线程调度机制分配的。而且这个分配会产生一定的内存开销,不过在JDK1.5之后,已经将这个开销降至很小,跟线程给我们带来的好处比起来,几乎可以忽略不计。

显然,如果通过这种方法使用线程,一个线程执行完之后,垃圾回收器会在某个时刻将这个对象回收,然后每次需要的时候,又要重新创建线程。显然,假如我们整个项目贯穿了线程,那么这将是一个可以进行效率优化的好地方。

JDK 1.5的java.util.concurrent包中的执行器(Executor)将可以帮我们管理Thread对象,那就是线程池,从而简化并发编程。

有多种方式设置我们的线程池,先不多说,且看他如何帮我们管理Thread对象。

 
———————忧郁又带着些调皮的分割线(可变大小线程池小例子)——————

 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {

    public static void main(String[] args) {
        
        //可变大小线程池
          ExecutorService exec = Executors.newCachedThreadPool();
          for (int i= 0; i<3; i++) {
              exec.execute(new ListOff());
          }
          exec.shutdown();
    }
}


执行结果:

 

#0(4).
#1(4).
#2(4).
#1(3).
#2(3).
#1(2).
#0(3).
#2(2).
#2(1).
#0(2).
#2(Liftoff!).
#0(1).
#1(1).
#0(Liftoff!).
#1(Liftoff!).


 

分析:其实执行结果和上一个的原理是一样的,但是呈现出来的结果不一样,因为线程的调度不是我们可以预测的。但为什么说使用了这个会闭上一个好呢?

 
假如线程贯穿了整个项目,那么,使用以上的方法,垃圾回收器自动回收线程,而是由ExecutorService管理,当一个线程执行完毕,将线程对象放到线程池,当我们需要线程的时候,他又从线程池中取出来,这样一来,假如线程用的多的话,我们就可以在创建线程,回收线程中节省大量的时间,从而提高效率。


但是,可能有人会问,怎么控制线程池中线程的数量呢?Good Question!每个项目的需求不一样,所需的线程数量也不一样,当我们需要设定线程熟的时候,且看我们的下一个例子:


————忧郁又带着些调皮的分割线(固定大小线程池小例子)————————

 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {

    public static void main(String[] args) {
        
        //固定大小线程池
        ExecutorService exec = Executors.newFixedThreadPool(2);
        for (int i= 0; i<3; i++) {
            exec.execute(new ListOff());
        }
        exec.shutdown();
    }
}


执行结果:

 

#1(4).
#0(4).
#0(3).
#0(2).
#0(1).
#0(Liftoff!).
#2(4).
#2(3).
#2(2).
#2(1).
#2(Liftoff!).
#1(3).
#1(2).
#1(1).
#1(Liftoff!).


 

分析:这个结果可就有意思了!我故意把线程数设置为2,而我们有三个线程,为了让我们更加了解线程池的原理,我们就来分析一下结果。


由于我们的线程池只能放两个线程,所以我们猜测,一次最多只能有两个线程同时执行,假如两个有一个执行完毕,才会进行第三个线程。执行结果和我们猜测的一样,先执行第0个,第1个线程,看,由于线程具有不可预测性,所以线程0和1执行的时候,是没有先后顺序的,比如我的执行结果就是先打印线程1.

当线程池把线程0执行完毕以后,我们的线程2才闪亮登场,并且,线程2比线程1还先结束。

 
最后我还要提一句,Sun还为们提供了单一线程池,为什么呢?假如我们具有不可线程共享的资源,我们就可以使用单一线程池,这样就可以不必使用锁了。不过具体的应用,由于本人才疏学浅,没有这方面的项目经验,目前还不得知,为了让读者最大化、系统化的了解线程池,我还是决定把它写下来,希望有一定耐心的人能看到(我假设很多人看不到这里,因为确实很长,很枯燥,加上我讲的不好),

实践是检验理论的唯一标准,上代码:

 

———————忧郁又带着些调皮的分割线(单一线程池小例子)——————

 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {

    public static void main(String[] args) {
       
        //单一线程池
        ExecutorService exec = Executors.newSingleThreadExecutor();
        for (int i= 0; i<3; i++) {
            exec.execute(new ListOff());
        }
        exec.shutdown();
    }
}


执行结果:

 

#0(4).
#0(3).
#0(2).
#0(1).
#0(Liftoff!).
#1(4).
#1(3).
#1(2).
#1(1).
#1(Liftoff!).
#2(4).
#2(3).
#2(2).
#2(1).
#2(Liftoff!).


 

分析:假如你已经猜到了结果,那么恭喜您,你已经基本了解线程池了。如果你没有猜到结果,那么,你可以去面壁。


小结:有点长,但其实不难,其实只知道了线程池,只是起步,假如想在实际项目中应用,那还是需要在实践中学习的,比如,你的线程池中的一个线程执行出现异常时,你该如何处理,假如你想要知道每个线程的执行结果,你该如何去做?希望你们能从本篇文章学会相关知识。


最后:由于本人才疏学浅,也是一枚还在实习的菜鸟,所讲解的东西肯定会有不足甚至有着严重的技术错误,欢迎大家批评指正,在交流中成长。如果有读者有不明白的地方,随时欢迎留言提问,咱们相互探讨一下,谢谢!

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值