Java线程池技术之二 Java自带线程池实现

 

一,介绍

  类视图如下:

 

  自Java 1.5后,Java对线程相关的库做了很大的拓展,线程池就是其中之一。Java线程的新特性多数在java.util.concurrent,其包含众多的接口和类。其中java.util.concurrent.Executor是这些类的最顶级接口。其是执行线程的一个工具。ExecutorService在Executor的基础上增加了一些方法,以向线程池提交任务。包括实现Runnable接口的任务和实现Callable接口的任务。两个方法为:

  Future<?> submit(Runnable task)

  Future<T> submit(Callable<T> task)

  实现Runnable接口的任务,其run方法的返回值为void,因此submit的返回值为Future<?>类型,?表示匹配任意一种类型。实现Callable<T>接口的任务,其call方法的返回值类型为T,对应submit的返回值类型则是Future<T>。具有返回值的Callable的任务对于多个线程中传递状态和结果是非常有用的。另外使用这两个方法返回的Future对象可以阻塞当前线程直到提交的任务运行完毕以获取结果,也可以用以取消任务的执行,或者检测任务是否被取消或者是否执行完毕。如果不使用Future,我们检测一个线程是否执行完毕通常使用Thread.join()或者对状态标识进行轮询。

  阻塞等待结果,或者在某个时候获取结果的方法是,调用Futrue的get()方法。这样调用线程便会等待在线程执行完成并将结果反馈到Futrue对象。之后调用线程可以对处理结果进行判别处理。isDone(),isCancelled()可以用以判断当前的任务执行状态(注意被打断或取消也是Done的一种)。cancel(Boolean)方法则可以用以取消线程执行。如果参数是true表示即使任务已经执行也将试图取消它,如果是false则表示如果任务没有被执行则取消,执行了则不取消执行。

  可以使用Executor的静态方法创建实现ExecutorService的线程池类。对应方法创建的线程池描述如下:

  1,newSingleThreadExecutor,单线程线程池。单个线程执行器,内有一个线程执行Runnable的任务。如果该线程发生异常终了,则创建新的进行补充。可以保证任务顺 序的执行。

  2,newFixedThreadPool,创建固定线程数目(参数指定)的线程池。每加入一个任务,创建一个线程,直到达到固定数目后,将会有固定大小的线程执行Runnable的任务。如果有线程异常终了,则创建新的线程来进行补充。

  3,newCachedThreadPool, 缓冲线程池,产生一个大小可变的线程池。当线程池的线程多于执行任务所需要的线程的时候,对空闲线程(即60s没有任务执行)进行回收;当执行任务的线程数不足的时候,自动拓展线程数量。因此线程数量是JVM可创建线程的最大数目。

  4,newSingleThreadScheduledExecutor,单个线程调度执行器,产生单个线程执行任务,采用schedule方法可以延期或者定期的执行任务

  5,newScheduledThreadPool,调度线程执行器,当有任务的时候,创建线程,直到默认线程数量(参数指定),当执行任务所需的线程多于该数目的时候,自动拓展线程数量,没有上限;如果线程空闲则被回收,直到默认线程数量。

  6,另外可以自定义线程池,请参见使用代码例子。

  另外注意CompletionService接口,其能够根据任务的执行先后顺序得到执行结果。


二,使用例子


//两种任务

package poolmanager;
public class RunnableTask implements Runnable {

    int i = 0;

    public RunnableTask(int i) {

        this.i = i;

    }

    public void run() {

        System.out.println("******************");
        System.out.println(Thread.currentThread().getName());

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Task is :" + i);

    }

}

package poolmanager;
import java.util.concurrent.Callable;

public  class CallableTask implements Callable<String> {

    public String ok = "OK!";
    public String ng = "Error Happened in Running!";

    int i = 0;

    public CallableTask(int i){
        this.i = i;
    }

    public String call(){

        System.out.println("******************");
        System.out.println(Thread.currentThread().getName());

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Task is :" + i);

        return ok;

    }

}

//线程管理类

package poolmanager;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class JavaThreadPoolManager {

    int ThreadSize = 2;
    int ThreadMaxSize = 2*ThreadSize;

    ExecutorService executorSingleThreadExecutor = null;
    ExecutorService executorFixedThreadPool = null;
    ExecutorService executorCachedThreadPool = null;
    ScheduledExecutorService executorSingleThreadScheduledExcutor = null;
    ScheduledExecutorService executorScheduledThreadPool = null;

    //自定义线程池
    BlockingQueue<Runnable> bqueue = null;
    ThreadPoolExecutor pool = null;

    public void startJavaThreadPoolManager(){


        //单个线程执行器,内有一个线程执行Runnable的任务。
        //如果该线程发生异常终了,则创建新的进行补充。
        //可以保证任务顺序的执行。
        executorSingleThreadExecutor = Executors.newSingleThreadExecutor();

        //固定数目的线程执行器。
        //每加入一个任务,创建一个线程,直到达到固定数目后,
        //将会有固定大小的线程执行Runnable的任务。
        //如果有线程异常终了,则创建新的线程来进行补充。
        executorFixedThreadPool = Executors.newFixedThreadPool(ThreadSize);

        //缓冲线程执行器,产生一个大小可变的线程池。
        //当线程池的线程多于执行任务所需要的线程的时候,
        //对空闲线程(即60s没有任务执行)进行回收;
        //当执行任务的线程数不足的时候,自动拓展线程数量。因此线程数量是JVM
        //可创建线程的最大数目。
        executorCachedThreadPool = Executors.newCachedThreadPool();

        //单个线程调度执行器,产生单个线程执行任务,采用schedule方法可以延期或者定期的执行任务
        executorSingleThreadScheduledExcutor = Executors
            .newSingleThreadScheduledExecutor();

        //调度线程执行器,当有任务的时候,创建线程,直到默认线程数量(参数指定),
        //当执行任务所需的线程多于该数目的时候,
        //自动拓展线程数量,没有上限;
        //如果线程空闲则被回收,直到默认线程数量。
        executorScheduledThreadPool = Executors.newScheduledThreadPool(ThreadSize);

        //自定义线程池
        bqueue = new ArrayBlockingQueue<Runnable>(20);
        //第三个参数是线程池线程所允许的空闲时间
        //第四个参数是线程池线程空闲时间的单位
        //第五个参数是缓冲任务队列
        //第六个参数是缓冲区满的时候,对任务的处理策略
        //第六个参数有如下几种选择:
        //ThreadPoolExecutor.AbortPolicy()
        //抛出java.util.concurrent.RejectedExecutionException异常
        //ThreadPoolExecutor.CallerRunsPolicy()
        //重试添加当前的任务,即自动以任务为参数,再次调用execute()方法
        //ThreadPoolExecutor.DiscardOldestPolicy()
        //抛弃一个已有的任务(抛弃当前任务队列的头部任务,即最开始加入的任务)
        //ThreadPoolExecutor.DiscardPolicy()
        //抛弃当前这个任务

        pool = new ThreadPoolExecutor(
                ThreadSize,ThreadMaxSize,2,TimeUnit.MILLISECONDS,bqueue
                ,new ThreadPoolExecutor.DiscardOldestPolicy());

    }

    public void endJavaThreadPoolManager(){

        executorSingleThreadExecutor.shutdown();
        executorFixedThreadPool.shutdown();
        executorCachedThreadPool.shutdown();
        executorSingleThreadScheduledExcutor.shutdown();
        executorScheduledThreadPool.shutdown();
        pool.shutdown();

    }

}

//使用

package poolmanager;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class MainThread {

    public static void main(String[] args) throws InterruptedException {

        JavaThreadPoolManager jThreadPoolManager = new JavaThreadPoolManager();

        jThreadPoolManager.startJavaThreadPoolManager();

        RunnableTask command1 = new RunnableTask(1);
        CallableTask command2 = new CallableTask(2);

        jThreadPoolManager.executorSingleThreadExecutor.execute(command1);
        jThreadPoolManager.executorSingleThreadExecutor.submit(command2);

        jThreadPoolManager.executorFixedThreadPool.execute(command1);
        jThreadPoolManager.executorFixedThreadPool.submit(command2);

        jThreadPoolManager.executorCachedThreadPool.execute(command1);
        jThreadPoolManager.executorCachedThreadPool.submit(command2);

        jThreadPoolManager.executorScheduledThreadPool.execute(command1);
        jThreadPoolManager.executorScheduledThreadPool.submit(command2);

        jThreadPoolManager.executorSingleThreadScheduledExcutor.execute(command1);
        jThreadPoolManager.executorSingleThreadScheduledExcutor.submit(command2);

        jThreadPoolManager.executorScheduledThreadPool.execute(command1);
        jThreadPoolManager.executorScheduledThreadPool.schedule(command1,60, TimeUnit.SECONDS);
        jThreadPoolManager.executorScheduledThreadPool.schedule(command2,60, TimeUnit.SECONDS);

        jThreadPoolManager.executorSingleThreadScheduledExcutor.scheduleWithFixedDelay(command1, 0, 20, TimeUnit.SECONDS);

        Future<String> returnFuture =
            jThreadPoolManager.executorSingleThreadScheduledExcutor.submit(command2);

        System.out.println(returnFuture.isCancelled());
        System.out.println(returnFuture.isDone());

        returnFuture.cancel(true);

        System.out.println(returnFuture.isCancelled());
        System.out.println(returnFuture.isDone());

        String resultString =null;

        if(!returnFuture.isCancelled()){

            try {
                resultString = returnFuture.get();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

            System.out.println(resultString);

        }

        jThreadPoolManager.pool.execute(command1);

    }

}

三,再讨论


  我们看到自定义线程池的时候,可以自定义任务队列,其中有如下几种任务队列的自定义方法:

  1,直接提交  选择任务队列为 SynchronousQueue的,则默认的队列大小为1,在这种情况下,如果没有空闲线程,则往往直接创建新的线程执行该任务,因此往往需要不设立线程的数目上限。该策略可以避免在处理可能具有内部依赖的请求集时出现锁,因为其任务执行顺序必然是先加入的先被执行。

  2,无界队列 选择任务队列为LinkedBlockingQueue 其意义即任务队列大小无限制。这样创建的线程往往不超过corePoolSize。因此maximumPoolSize的值就没有什么意义。适合于任务相互独立。

  3,有界队列 使用具有最大线程数目限制maximumPoolSize的时候,有界队列例如ArrayBlockingQueue有助于防止资源耗尽,但可能较难控制。

  使用大队列小池子有利于降低CPU使用,上下文切换,但是吞吐量不高;如果小队列大池子,则CPU使用率高,但是可能调度开销大,也会降低吞吐量。

   另外,选择上述任务队列的方案,需要与corePoolSize和maximumPoolSizes这对参数配合。

 

  因此,线程池的选择要根据任务的类型,比如任务是数量大、单个执行时间短,还是任务数量小、单个执行长,还是任务数量又多,执行时间又长,来进行不同的选择。而自定义线程池也是需要根据任务的类型选择任务队列、corePoolSize和maximumPoolSizes。

  此外,keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。如果很容易就产生线程的回收,也会导致性能下降。





参考:

  1,JDK Document

  2, http://www.cnblogs.com/jersey/archive/2011/03/30/2000231.html


  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java线程池是一种用于管理和复用线程的机制,它可以提高多线程应用程序的性能和效率。线程池中的线程可以被重复使用,避免了频繁创建和销毁线程的开销。 在Java中,可以使用java.util.concurrent包中的Executor框架来创建和管理线程池线程池的大小可以根据应用程序的需求进行调整,以平衡线程的数量和系统资源的利用率。 要进行Java线程池的调优,可以考虑以下几个方面: 1. 线程池的大小:线程池的大小应该根据应用程序的负载和系统资源进行调整。如果线程池的大小过小,可能会导致任务排队等待执行;如果线程池的大小过大,可能会导致系统资源的浪费。可以通过监控线程池的活动线程数和任务队列的长度来确定合适的线程池大小。 2. 任务队列的选择:线程池中的任务队列可以选择不同的实现,如ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等。不同的任务队列有不同的特点,可以根据任务的特性和应用程序的需求选择合适的队列类型。 3. 线程池的拒绝策略:当线程池无法接受新的任务时,可以通过设置拒绝策略来处理。可以选择抛出异常、丢弃任务、丢弃最旧的任务或者调用者自己处理被拒绝的任务。 4. 线程池的生命周期管理:线程池的生命周期包括初始化、运行和关闭三个阶段。在使用完线程池后,应该及时关闭线程池,释放资源。 5. 监控和调优:可以通过监控线程池的活动线程数、任务队列的长度、任务执行时间等指标来进行调优。可以使用Java自带的监控工具,如JConsole、VisualVM等,或者使用第三方的监控工具。 以上是Java线程池调优的一些常见方法和注意事项。根据具体的应用场景和需求,可能还需要进行其他的调优措施。希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值