Java并发编程——分析使用线程池

 

1.概述ThreadPoolExecutor类的构造方法

  java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们以jdk1.6中的ThreadPoolExecutor类举例。

  在ThreadPoolExecutor类中提供了四个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

   从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

2.下面解释构造器中各个参数的含义

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

	TimeUnit.DAYS;               //天
	TimeUnit.HOURS;             //小时
	TimeUnit.MINUTES;           //分钟
	TimeUnit.SECONDS;           //秒
	TimeUnit.MILLISECONDS;      //毫秒
	TimeUnit.MICROSECONDS;      //微妙
	TimeUnit.NANOSECONDS;       //纳秒
  • workQueue:一个任务队列,用来存储等待执行的任务,这个参数的选择也很重要,有以下几种选择:
  • 	ArrayBlockingQueue
    	PriorityBlockingQueue 
    	LinkedBlockingQueue
    	synchronousQueue
    
  • threadFactory:线程工厂,主要用来创建线程;
  • handler:表示当拒绝处理任务时的策略,有以下四种取值:
  •     AbortPolicy               
        CallerRunsPolicy               
        DiscardPolicy                
        DiscardOldestPolicy            
    

    3.下面看ThreadPoolExecutor类的继承图

    从上面给出的ThreadPoolExecutor类的代码可以知道,ThreadPoolExecutor继承了AbstractExecutorService,我们来看一下抽象类AbstractExecutorService:

    public abstract class AbstractExecutorService implements ExecutorService {
    
        protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
        protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
        public Future<?> submit(Runnable task) {};
        public <T> Future<T> submit(Runnable task, T result) { };
        public <T> Future<T> submit(Callable<T> task) { };
        private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                                boolean timed, long nanos)
            throws InterruptedException, ExecutionException, TimeoutException {
        };
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException {
        };
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                               long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {
        };
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
            throws InterruptedException {
        };
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                             long timeout, TimeUnit unit)
            throws InterruptedException {
        };
    }
    

    AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。 我们接着看ExecutorService接口:

    public interface ExecutorService extends Executor {
     
        void shutdown();
        boolean isShutdown();
        boolean isTerminated();
        boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException;
        <T> Future<T> submit(Callable<T> task);
        <T> Future<T> submit(Runnable task, T result);
        Future<?> submit(Runnable task);
        <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
            throws InterruptedException;
        <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                      long timeout, TimeUnit unit)
            throws InterruptedException;
     
        <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException;
        <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                        long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }
    

    ExecutorService又是继承了Executor接口,我们看一下Executor接口

    public interface Executor {
        void execute(Runnable command);
    }
    

    execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

      submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。

      shutdown()和shutdownNow()是用来关闭线程池的。

      还有很多其他的方法,有兴趣的朋友可以自行查阅API。

    4.线程池中的线程初始化

      默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

      在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

    • prestartCoreThread():初始化一个核心线程;
    • prestartAllCoreThreads():初始化所有核心线程

    5.一般情况下线程池处理任务的逻辑

    • 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
    • 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
    • 如果当前线程池中的线程数目达到maximumPoolSize(队列早满了,线程们都忙着),此时达到线程池饱和状态,就会采取任务拒绝策略进行处理;

    6.任务缓存队列及排队策略

       1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
    
       2)PriorityBlockingQueue :能设置队列里任务的被处理的优先级。创建时即使指定了此队列大小,它还是会用Integer.MAX_VALUE;
    
      3)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
    
      4)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
    
    
    ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和synchronousQueue。
     
    我们使用的时候一般会指定队列大小,因为如果使用默认的Integer.MAX_VALUE,如果并发访问量太大的情况下,队列里会一直堆加新任务,最终可能把内存搞崩。
    

    7.任务拒绝策略

      当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

        AbortPolicy:默认拒绝策略,线程池饱和状态,则会报错终止(会处理完正在处理的老任务和队列里的任务们);
        
        CallerRunsPolicy: 线程池饱和状态时,则会推迟新任务,等待忙完的线程集中处理队列里的任务,然后再处理刚才推迟的新任务,然后再往任务队列里加....一直到处理完所有任务;
        
        DiscardPolicy:线程池饱和状态时,就会放弃新任务,会把老任务们处理完。
        
        DiscardOldestPolicy  :从任务队列总弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列,再弹出再加入再弹出再加入,即会放弃一些运气不好的任务。
    
    
    
    拓展:**线程池饱和状态**
    任务队列满了(即不能先缓一缓任务了)、线程数达到最大值maximumPoolSize了(即不能新建线程)、线程都忙着。这时候处理不了、又存不了,就得考虑拒绝新任务的策略了,是直接异常、还是先推迟新任务、还是放弃新任务、还是不断地从队列里弹出一个老任务腾出一个位子,加入一个新任务
    
    拓展:线程的存活:
    如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
        
    

    8.线程池的关闭

      ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

    • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
    • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

    9.线程池容量的动态调整

      ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

    • setCorePoolSize:设置核心池大小
    • setMaximumPoolSize:设置线程池最大能创建的线程数目大小

      当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

    二.使用示例

    1.使用ArrayBlockingQueue、使用默认拒绝策略AbortPolicy

    看输出结果
    你会发现此拒绝策略是通过报错来终止的。
    package com.amar.batch;
    
    import java.io.IOException;
    import java.util.Properties;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class TestThreadPool {
    	// main方法里定义一个线程池
    	// 虽然有jdk封装好的方法创建线程池,但是我们一般都会根据自己的并发量和服务器性能进行自己的配置
    	//创建内部类的实例:父类的实例. new 子类构造方法。 如			MyTask task = new TestThreadPool().new MyTask(i);
    	// new ThreadPoolExecutor.AbortPolicy(),这里是因为AbortPolicy类是static修饰的,可以直接.构造方法得到一个实例
    	public static void main(String[] args) throws IOException {
    		Properties pro = new Properties();
    		pro.load(TestThreadPool.class.getClassLoader().getResourceAsStream("ThreadPoolExeConfig.properties"));
    		
    		ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,5000, TimeUnit.MILLISECONDS,
    				new ArrayBlockingQueue<Runnable>(5),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    		
    		for (int i = 1; i < 30; i++) {
    			MyTask task = new TestThreadPool().new MyTask(i);
    			executor.execute(task);
    			
    			
    			System.out.println("线程池中线程数量有 :"+executor.getPoolSize()+"  个,队列中等待执行的任务数目有:"+
    		             executor.getQueue().size()+"  个,执行任务数目总量有:"+executor.getTaskCount()+"  个"
    		             +"---正在执行任务的线程的大概数量"+executor.getActiveCount()+"  池中存在的最大线程数: "+executor.getLargestPoolSize()
    		             );
    		}
    		
    		
    	}
    	
    	//创建一个内部类作为任务类,任务类也必须是个线程类。
    	class MyTask implements Runnable{
    		private int num;
    		
    		public MyTask(int num) {
    			super();
    			this.num = num;
    		}
    
    		@Override
    		public void run() {
    			System.out.println("第"+this.num+"个任务开始执行");
    			try {
    				Thread.currentThread().sleep(3000);//这里模拟真实场景中一个任务可能要执行3秒
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println("第"+this.num+"个任务执行完毕");
    		}
    		
    	}
    }
    
    

    输出:

    1个任务开始执行
    线程池中线程数量有 1  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:1  个---正在执行任务的线程的大概数量1  池中存在的最大线程数: 1
    线程池中线程数量有 2  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:2  个---正在执行任务的线程的大概数量2  池中存在的最大线程数: 2
    2个任务开始执行
    线程池中线程数量有 3  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:3  个---正在执行任务的线程的大概数量3  池中存在的最大线程数: 3
    3个任务开始执行
    线程池中线程数量有 4  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:4  个---正在执行任务的线程的大概数量4  池中存在的最大线程数: 4
    4个任务开始执行
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:5  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:1  个,执行任务数目总量有:6  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    5个任务开始执行
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:2  个,执行任务数目总量有:7  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:3  个,执行任务数目总量有:8  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:4  个,执行任务数目总量有:9  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:5  个,执行任务数目总量有:10  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 6  个,队列中等待执行的任务数目有:5  个,执行任务数目总量有:11  个---正在执行任务的线程的大概数量6  池中存在的最大线程数: 6
    11个任务开始执行
    线程池中线程数量有 7  个,队列中等待执行的任务数目有:5  个,执行任务数目总量有:12  个---正在执行任务的线程的大概数量7  池中存在的最大线程数: 7
    12个任务开始执行
    线程池中线程数量有 8  个,队列中等待执行的任务数目有:5  个,执行任务数目总量有:13  个---正在执行任务的线程的大概数量8  池中存在的最大线程数: 8
    13个任务开始执行
    线程池中线程数量有 9  个,队列中等待执行的任务数目有:5  个,执行任务数目总量有:14  个---正在执行任务的线程的大概数量9  池中存在的最大线程数: 9
    14个任务开始执行
    线程池中线程数量有 10  个,队列中等待执行的任务数目有:5  个,执行任务数目总量有:15  个---正在执行任务的线程的大概数量10  池中存在的最大线程数: 10
    15个任务开始执行
    Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.amar.batch.TestThreadPool$MyTask@4aa298b7 rejected from java.util.concurrent.ThreadPoolExecutor@7d4991ad[Running, pool size = 10, active threads = 10, queued tasks = 5, completed tasks = 0]
    	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
    	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
    	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
    	at com.amar.batch.TestThreadPool.main(TestThreadPool.java:24)
    1个任务执行完毕
    6个任务开始执行
    2个任务执行完毕
    7个任务开始执行
    4个任务执行完毕
    3个任务执行完毕
    9个任务开始执行
    5个任务执行完毕
    8个任务开始执行
    10个任务开始执行
    11个任务执行完毕
    13个任务执行完毕
    14个任务执行完毕
    12个任务执行完毕
    15个任务执行完毕
    7个任务执行完毕
    6个任务执行完毕
    9个任务执行完毕
    8个任务执行完毕
    10个任务执行完毕
    
    

    2.使用PriorityBlockingQueue、使用默认拒绝策略AbortPolicy

    看输出结果
    你会发现PriorityBlockingQueue即使指定值为1,它还是以Integer.max为值;
    你会发现它会先处理值大的任务。
    public class TestThreadPool2 {
    	public static void main(String[] args) throws IOException {
    		Properties pro = new Properties();
    		pro.load(TestThreadPool2.class.getClassLoader().getResourceAsStream("ThreadPoolExeConfig.properties"));
    		
    		ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,9000,TimeUnit.MILLISECONDS,
    				new PriorityBlockingQueue<Runnable>(1),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    		for (int i = 1; i < 21; i++) {
    			MyTask task = new TestThreadPool2().new MyTask(i);
    			executor.execute(task);
    			
    			
    			System.out.println("线程池中线程数量有 :"+executor.getPoolSize()+"  个,队列中等待执行的任务数目有:"+
    		             executor.getQueue().size()+"  个,执行任务数目总量有:"+executor.getTaskCount()+"  个"
    		             +"---正在执行任务的线程的大概数量"+executor.getActiveCount()+"  池中存在的最大线程数: "+executor.getLargestPoolSize()
    		             );
    		}
    		for (int i = 101; i < 121; i++) {
    			MyTask task = new TestThreadPool2().new MyTask(i);
    			executor.execute(task);
    			
    			
    			System.out.println("线程池中线程数量有 :"+executor.getPoolSize()+"  个,队列中等待执行的任务数目有:"+
    					executor.getQueue().size()+"  个,执行任务数目总量有:"+executor.getTaskCount()+"  个"
    					+"---正在执行任务的线程的大概数量"+executor.getActiveCount()+"  池中存在的最大线程数: "+executor.getLargestPoolSize()
    					);
    		}
    		
    		
    	}
    	
    	//创建一个内部类作为任务类,任务类也必须是个线程类。
    	class MyTask implements Runnable ,Comparable<MyTask> {
    		private int num;
    		
    		public MyTask(int num) {
    			super();
    			this.num = num;
    		}
    		
    
    		public int getNum() {
    			return num;
    		}
    
    
    		@Override
    		public void run() {
    			System.out.println("第"+this.num+"个任务开始执行");
    			try {
    				Thread.currentThread().sleep(3000);//这里模拟真实场景中一个任务可能要执行1秒
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println("第"+this.num+"个任务执行完毕");
    		}
    
    		@Override
    		public int compareTo(MyTask o) {
    			if (this.getNum()>o.getNum()) { //如果大于,返回-1
    				return -1;
    			}
    			if (this.getNum()<o.getNum()) {
    				return 1;
    			}
    			return 0;
    		}
    		
    	}
    }
    
    

    输出:

    1个任务开始执行
    线程池中线程数量有 1  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:1  个---正在执行任务的线程的大概数量1  池中存在的最大线程数: 1
    线程池中线程数量有 2  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:2  个---正在执行任务的线程的大概数量2  池中存在的最大线程数: 2
    2个任务开始执行
    3个任务开始执行
    线程池中线程数量有 3  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:3  个---正在执行任务的线程的大概数量3  池中存在的最大线程数: 3
    线程池中线程数量有 4  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:4  个---正在执行任务的线程的大概数量4  池中存在的最大线程数: 4
    4个任务开始执行
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:0  个,执行任务数目总量有:5  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    5个任务开始执行
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:1  个,执行任务数目总量有:6  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:2  个,执行任务数目总量有:7  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:3  个,执行任务数目总量有:8  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:4  个,执行任务数目总量有:9  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:5  个,执行任务数目总量有:10  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:6  个,执行任务数目总量有:11  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:7  个,执行任务数目总量有:12  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:8  个,执行任务数目总量有:13  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:9  个,执行任务数目总量有:14  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:10  个,执行任务数目总量有:15  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:11  个,执行任务数目总量有:16  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:12  个,执行任务数目总量有:17  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:13  个,执行任务数目总量有:18  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:14  个,执行任务数目总量有:19  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:15  个,执行任务数目总量有:20  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:16  个,执行任务数目总量有:21  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:17  个,执行任务数目总量有:22  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:18  个,执行任务数目总量有:23  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:19  个,执行任务数目总量有:24  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:20  个,执行任务数目总量有:25  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:21  个,执行任务数目总量有:26  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:22  个,执行任务数目总量有:27  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:23  个,执行任务数目总量有:28  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:24  个,执行任务数目总量有:29  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:25  个,执行任务数目总量有:30  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:26  个,执行任务数目总量有:31  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:27  个,执行任务数目总量有:32  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:28  个,执行任务数目总量有:33  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:29  个,执行任务数目总量有:34  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:30  个,执行任务数目总量有:35  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:31  个,执行任务数目总量有:36  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:32  个,执行任务数目总量有:37  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:33  个,执行任务数目总量有:38  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:34  个,执行任务数目总量有:39  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    线程池中线程数量有 5  个,队列中等待执行的任务数目有:35  个,执行任务数目总量有:40  个---正在执行任务的线程的大概数量5  池中存在的最大线程数: 5
    2个任务执行完毕
    1个任务执行完毕
    120个任务开始执行
    119个任务开始执行
    3个任务执行完毕
    118个任务开始执行
    4个任务执行完毕
    5个任务执行完毕
    116个任务开始执行
    117个任务开始执行
    119个任务执行完毕
    115个任务开始执行
    118个任务执行完毕
    114个任务开始执行
    120个任务执行完毕
    113个任务开始执行
    117个任务执行完毕
    112个任务开始执行
    116个任务执行完毕
    111个任务开始执行
    114个任务执行完毕
    110个任务开始执行
    113个任务执行完毕
    109个任务开始执行
    115个任务执行完毕
    108个任务开始执行
    112个任务执行完毕
    107个任务开始执行
    111个任务执行完毕
    106个任务开始执行
    109个任务执行完毕
    105个任务开始执行
    107个任务执行完毕
    106个任务执行完毕
    103个任务开始执行
    110个任务执行完毕
    102个任务开始执行
    108个任务执行完毕
    101个任务开始执行
    104个任务开始执行
    104个任务执行完毕
    20个任务开始执行
    102个任务执行完毕
    19个任务开始执行
    105个任务执行完毕
    18个任务开始执行
    103个任务执行完毕
    17个任务开始执行
    101个任务执行完毕
    16个任务开始执行
    19个任务执行完毕
    20个任务执行完毕
    17个任务执行完毕
    18个任务执行完毕
    13个任务开始执行
    14个任务开始执行
    15个任务开始执行
    12个任务开始执行
    16个任务执行完毕
    11个任务开始执行
    15个任务执行完毕
    10个任务开始执行
    13个任务执行完毕
    9个任务开始执行
    12个任务执行完毕
    8个任务开始执行
    14个任务执行完毕
    7个任务开始执行
    11个任务执行完毕
    6个任务开始执行
    10个任务执行完毕
    9个任务执行完毕
    8个任务执行完毕
    7个任务执行完毕
    6个任务执行完毕
    
    

      虽然我们更喜欢自己直接使用ThreadPoolExecutor, 不过在java doc中,并不提倡我们直接使用,而是使用Executors类中提供的几个静态方法来创建线程池:

    Executors.newCachedThreadPool();        //XX池,核心池0个,最大池最多有Integer.MAX_VALUE个。队列为SynchronousQueue即不入队列,来一个任务建一个线程。
    Executors.newSingleThreadExecutor();   //固定池,核心池和最大池都是有且仅有一个线程,队列为LinkedBlockingQueue,且使用默认队列大小Integer.MAX_VALUE。
    Executors.newFixedThreadPool(int);    //固定池,核心池和最大池都是有传参nThreads个线程,队列为LinkedBlockingQueue,且使用默认队列大小Integer.MAX_VALUE。
    

      下面是这三个静态方法的具体实现;

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

      从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。

      newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

      newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;

      newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

      另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。

    四.如何合理配置线程池的大小

      本节来讨论一个比较重要的话题:如何合理配置线程池大小,仅供参考。

      一般需要根据任务的类型来配置线程池大小:

      如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1

      如果是IO密集型任务,参考值可以设置为2*NCPU

      当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

    CPU密集型 vs IO密集型
    我们可以把任务分为计算密集型和IO密集型。
    
    计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
    
    计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
    
    第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务(数据库操作就是磁盘IO操作哦),这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。IO密集型任务则由于线程并不是一直在执行任务,则配置尽可能多的线程,如2*CPU数。对于IO密集型任务,线程越多,CPU使用率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
    
    选择开发语言:
    IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最不值当,因为C语言开发难。
    

    五.其他拓展

    1.为什么C语言运行比Python快?

    python的传统运行执行模式:录入的源代码(HelloWorld.py)转换(编译)为字节码(HelloWorld.pyc),之后字节码在python虚拟机(叫PVM)中运行,再解释成机器码在CPU中执行。
    c编译器直接把c源代码编译成机器码。过程比python执行过程少了字节码生成和虚拟机执行字节码过程。所以自然比python快。

    2.查看服务器硬件的物理CPU数、单个CPU是几核、逻辑CPU数
    2.1:lunix:

    物理CPU数:
    cat /proc/cpuinfo | grep "physical id"| sort |uniq | wc -l
    
    查看单个CPU是几核:
    cat /proc/cpuinfo | grep "cores" | uniq
    
    查看逻辑CPU数:
    cat /proc/cpuinfo | grep "processor" | wc -l
    

    2.2:Windos:

    只需要打开任务管理器,性能菜单。 比如我自己的电脑就是一个CPU、这个CPU上有6个能处理数据的芯片组(核)、又支持超线程技术,即逻辑cpu=1*6*2=12.
    (打开资源监视器也在任务管理器,性能菜单这里。)
    

    2.3首先要明确物理cpu个数、核数、逻辑cpu数的概念

    1.物理cpu数:主板上实际插入的cpu数量,可以数不重复的 physical id 有几个(physical id),一般都是一个。
    2.cpu核数:单块CPU上面能处理数据的芯片组的数量,如双核、四核、六核、八核等 (cpu cores)
    3.逻辑cpu数:一般情况下,逻辑cpu=物理CPU个数×每颗核数,如果不相等的话,则表示服务器的CPU支持超线程技术(HT:简单来说,它可使处理器中的1 颗内核如2 颗内核那样在操作系统中发挥作用。这样一来,操作系统可使用的执行资源扩大了一倍,大幅提高了系统的整体性能,此时逻辑cpu=物理CPU个数×每颗核数x2)
    

    参考:
    Java并发编程:线程池的使用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值