Java线程池使用

1.  为什么要用线程池?

      在Java中,如果每当一个请求到达就创建一个新线程,开销是相当大的。在实际使用中,每个请求创建新线程的服务器

在创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在实际处理实际的用户请求的时间和资源要多的多。除

了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个JVM中创建太多的线程,可能会导致系统由于

过度消耗内存或者“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻

处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象

来进行服务,这就是“池化资源”技术产生的原因。

     线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任

务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以立即请求服务,使应用程序响

应更快。另外,通过适当的调整线程池中的线程数据可以防止出现资源不足的情况。

     网上好多的介绍基本上都是基于这个主要原因。

2. ThreadPoolExecutor类

      线程池的关系图

Java线程池使用_第1张图片

       Java中的线程池技术主要用的是ThreadPoolExecutor 这个类。先来看这个类的构造函数,

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,

BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

    corePoolSize       线程池维护线程的最少数量

    maximumPoolSize    线程池维护线程的最大数量 

    keepAliveTime      线程池维护线程所允许的空闲时间  

    workQueue          任务队列,用来存放我们所定义的任务处理线程

    threadFactory      线程创建工厂

    handler            线程池对拒绝任务的处理策略

     ThreadPoolExecutor 将根据 corePoolSize和 maximumPoolSize 设置的边界自动调整池大小。当新任务在方法

execute(Runnable) 中提交时, 如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是

空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程,如果没满的话,那么就把Runnable放入队

列,等待执行; 如果设置的corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。

     ThreadPoolExecutor是Executors类的实现Executors类里面提供了一些静态工厂,生成一些常用的线程池,主

要有以下几个:

     newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行

所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它(意思是不同的线程id)。此线程池保证所有任务的执行顺序按照任

务的提交顺序执行。  

     newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线

程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

     newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,(意思是任务线程比线线程池大小小的

话)那么就会回收部分空闲(60秒不执行任务)的线程(所以只有newCachedThreadPool才设置了KeepALiveTime),当任务数增加时,此线程池

又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

      在实际的项目中,我们会使用得到比较多的是newFixedThreadPool,创建固定大小的线程池,但是这个方法在真实的线上

环境中还是会有很多问题,这个将会在下面一节中详细讲到。

      当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接

口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。

      1)CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

          public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

             if (!e.isShutdown()) {

                 r.run();

            }

        }

这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。

     2)AbortPolicy处理程序遭到拒绝将抛出运行时 RejectedExecutionException

         public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

              throw new RejectedExecutionException();

        }

 这种策略直接抛出异常,丢弃任务。

      3)DiscardPolicy不能执行的任务将被删除

          public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}

   这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。

     4)DiscardOldestPolicy如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,

则重复此过程)

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

            if (!e.isShutdown()) {

                e.getQueue().poll();

                e.execute(r);

            }

        }

      该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略

需要适当小心。

 

3.  ThreadPoolExecutor无界队列使用

  

 public class ThreadPool {

        private final static String poolName = "mypool";

        static private ThreadPool threadFixedPool = new ThreadPool(2);

       private ExecutorService executor;

      static public ThreadPool getFixedInstance() {

           return threadFixedPool;

       }

    private ThreadPool(int num) {

           executor = Executors.newFixedThreadPool(num, new DaemonThreadFactory(poolName));

}

public void execute(Runnable r) {

           executor.execute(r);

}

public static void main(String[] params) {

           class MyRunnable implements Runnable {

                    public void run() {

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

                             try {

                                       Thread.sleep(10);

                             } catch (InterruptedException e) {

                                       e.printStackTrace();

                             }

                    }

           }

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

             ThreadPool.getFixedInstance().execute(new MyRunnable());

           }

           try {

                    Thread.sleep(2000);

                    System.out.println("Process end.");

           } catch (InterruptedException e) {

                    e.printStackTrace();

           }

}

}

       在这段代码中,我们发现我们用到了Executors.newFixedThreadPool()函数,这个函数的实现是这样子的:

return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); 

       它实际上是创建了一个无界队列的固定大小的线程池。执行这段代码,我们发现所有的任务都正常处理了。但是在真实的线上环

境中会存在这样的一个问题,前端的用户请求源源不断的过来,后端的处理线程如果处理时间变长,无法快速的将用户请求处理

完返回结果给前端,那么任务队列中将堵塞大量的请求。这些请求在前端都是有超时时间设置的,假设请求是通过套接字过来,

当我们的后端处理进程处理完一个请求后,从队列中拿下一个任务,发现这个任务的套接字已经无效了,这是因为在用户端已经

超时,将套接字建立的连接关闭了。这样一来我们这边的处理程序再去读取套接字时,就会发生I/0 Exception. 恶性循环,导致我

们所有的处理服务线程读的都是超时的套接字,所有的请求过来都抛I/O异常,这样等于我们整个系统都挂掉了,已经无法对外提供

正常的服务了。

     对于海量数据的处理,现在业界都是采用集群系统来进行处理,当请求的数量不断加大的时候,我们可以通过增加处理节点,反正现

在硬件设备相对便宜。但是要保证系统的可靠性和稳定性,在程序方面我们还是可以进一步的优化的,我们下一节要讲述的就是针对

线上出现的这类问题的一种处理策略。

  上面newFixedThreadPool使用的是无界队列LinkedBlockingQueue,下面newCachedThreadPool使用的也是无界队列确实SynchronousQueue,最大线程池有几个就能运行几个,如果创建的线程数多于4040左右吧(我的pc是这样的)就内存溢出了

public class ThreadPoolTest {
	
	private static ExecutorService mExcutorService;
	private static final int SLEEP_TIME = 5000;
	private static final int THREAD_COUNT = 4040;
	public static void generateExecutor(){
		
		mExcutorService  =  Executors.newCachedThreadPool();
		
	}
	
	public static void main(String[] args) {    
		ThreadPoolTest test = new ThreadPoolTest();
	  
		generateExecutor();
		
		try {
			for(int i=0;i<THREAD_COUNT;i++){
				mExcutorService.execute(test.new WorkTask());
			} 
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
 
	
	class WorkTask implements Runnable{

		public void run() {
			
			try {
				System.out.println(""+Thread.currentThread().getName()+"woring");
				Thread.sleep(SLEEP_TIME)
);
				System.out.println(""+Thread.currentThread().getName()+"stop woring");
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	
	
}
4、ThreadPool用固定队列 ArrayBlockingQueue


public class ThreadPoolTest {
	
	private static ExecutorService mExcutorService;
	private static final int SLEEP_TIME = 5000;
	private static final int THREAD_COUNT = 13;
	public static void generateExecutor(){
	
		ArrayBlockingQueue<Runnable> mArrayBlockingQueue = new ArrayBlockingQueue<Runnable>(8);
		mExcutorService = new ThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS, mArrayBlockingQueue);
	}
	
	public static void main(String[] args) {    
		ThreadPoolTest test = new ThreadPoolTest();
	  
		generateExecutor();
		
		try {
			for(int i=0;i<THREAD_COUNT;i++){
				mExcutorService.execute(test.new WorkTask());
			} 
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		
	}
 
	
	class WorkTask implements Runnable{

		public void run() {
			
			try {
				System.out.println(""+Thread.currentThread().getName()+"woring");
				Thread.sleep(10);
				System.out.println(""+Thread.currentThread().getName()+"stop woring");
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	
	
}



方法 execute(Runnable) 中提交时, 如果运行的线程少于 corePoolSize,则创建新线程来处理请求。 如果运行的线程多于

corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程,如果此时线程数量达到maximumPoolSize,并且队

列已经满,就会拒绝继续进来的请求。

现在是13个已经超出最大线程数+队列的存储数,那么第13个线程就会被拒绝而发生异常,如果线程数调整为11个那么就有固定的3个线程执行(

创建新线程来处理请求, 如果运行的线程多于corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程).


你可能感兴趣的:(Java线程池使用)