Java高效并发(七)----jdk并发包的线程池参数详解

在上一篇谈到了线程池的参数代表的意义:

 

 从创建线程池的几个方法来看,后面两个参数都是有默认的参数

接下来本篇将对workQueue,threadFactory,handler这三个参数再深入研究

 workQueue(任务队列)

它是BlockingQueue接口的一个对象,用于存放Runnable对象,在ThreadPoolExecutor构造方法中可以使用下面几种BlockingQueue来实现不同的线程池,也就是使用不同的实现BlockingQueue接口的接口对象

(1)直接提交队列

由SynchronousQueue对象提供,这个队列当下有且只有一个任务,当有一个新任务,它就会直接提交给线程池,若这个时候没有空余线程就尝试创建线程执行,若此时线程数量没有超过最大线程数,线程池创建线程执行它,达到最大线程数执行拒绝策略。这样如果没有很大的maximumPoolSize,很容易执行拒绝策略

(2)优先任务队列

由PriorityBlockingQueue对象提供,是一个有优先级的任务队列,其他队列都是先进先出算法,这个是一个可以根据任务自身的优先级被空闲的线程执行,是一个特殊的无界队列

(3)有界的任务队列

由ArrayBlockingQueue对象实现,队列可容纳的任务数量是有限的,每次有新任务,若没有空闲线程且线程数已经达到corePoolSize就把它加到任务队列中,直到任务队列满员时,查看线程数是否达到maximumPoolSize,没有则创建线程执行任务,不会和空余下来的线程产生冲突。若达到maximumPoolSize对后面的任务执行拒绝策略。

(4)无界的任务队列

由LinkedBlockingQueue对象实现,队列可容纳的任务数量是没有上限的,每次有新任务,若没有空闲线程可以执行且线程数已经达到corePoolSize就把它加到任务队列中,系统的线程数达到corePoolSize的数量就不会再增加线程数,这样任务多的情况下很容易导致耗尽系统内存,因为任务队列无界

回顾前面使用Executors创建线程池的几个方法

newFixedThreadPool:创建固定大小数量的线程池,就是corePoolSize=maximumPoolSize,它的任务队列使用的无界的,LingkedBlockingQueue。

newSingleThreadPool:是newFixedThreadPool的一种退化,将线程数量设置为1.

newCachedThreadPool:可根据实际情况调整线程数量的线程池,就是把corePoolSzie设置为0,maximumPoolSize设置为无穷大,然后将任务队列设置为直接提交队列(SynchronousQueue),设置空余线程的存活时间是60秒,当有新任务提交就创建线程执行,回来没有任务,60秒会回收这个线程

拒绝策略(RejectedExecutionHandler)

jdk提供了四种内置的拒绝策略:

  • AbortPolicy:直接抛出异常,阻止系统正常工作
  • CallerRunsPolicy:只要线程池未关闭,就从正在运行的线程池中拿一个过来执行它,不会丢弃
  • DiscardOldestPoily:丢弃任务队列等待最久也就是下一次即将要执行的任务,然后准备再次提交
  • DiscardPoily:直接丢弃任务

在创建线程池时,默认的拒绝策略是第一种:defaultHandler是ThreadPoolExecutor类的一个静态常态

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

这些拒绝策略都是实现了RejectedExcutionHandler接口,我们可以通过匿名内部类实现这个接口,重写接口中的方法,自己定义拒绝策略。这个接口只有一个方法,r表示要执行拒绝策略的任务,executor表示当前线程池。

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

如下,这个例子展示如何自定义拒绝策略,自定义策略,比起内置的discard policy策略,就是加上打印日志,丢弃几个,程序中线程池的线程数量不可变是2个,等待队列定义为5个,提交20个任务,每个任务的执行时间比较快,100毫秒,那么等待队列基本很快就会满,所以结果如下:

package xidian.lili.reentrantlock;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadPool {
	public static class MyTask implements Runnable{

		@Override
		public void run() {
			try {
				System.out.println(Thread.currentThread().getId()+" is running");
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}		
		}	
	}
	public static void main(String[] args) throws InterruptedException {
		MyTask task=new MyTask();
		ExecutorService pool=new ThreadPoolExecutor(2,2,0L,TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),
				new RejectedExecutionHandler(){
					@Override
					public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
						System.out.println(r.toString()+" is discard");						
					}			
		});
		for(int i=0;i<20;i++){
			pool.execute(task);
			Thread.sleep(10);
		}
	}

}

 线程工厂(ThreadFactory)

那么在我们创建线程池时,我么输入参数当前线程数量,创建好的线程池可以在运行时新建线程,那是谁负责创建出线程呢,就是接下来要说的ThreadFactory。它是一个接口,只有一个方法,用来创建线程。应用工厂模式。

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

在ThreadPoolExecutor中有内置的默认线程工厂参数,就是调用Executors类中的defaultThreadFactory(),返回一个ThreadFactory对象

 public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }

而defaultThreadFactory是Executors的一个静态内部类,它实现了ThreadFactory接口,重写了里面的Thread newThread(Runnable r);方法。

那跟自定义拒绝策略一样我们也可以自定义线程工厂:我们使用刚才那个程序,只是把拒绝策略换成默认的,然后自定义线程工厂

package xidian.lili.reentrantlock;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadFactoryDemo {

	public static class MyTask implements Runnable{

		@Override
		public void run() {
			try {
				System.out.println(Thread.currentThread().getId()+" is running");
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}		
		}	
	}
	public static void main(String[] args) throws InterruptedException {
		MyTask task=new MyTask();
		ExecutorService pool=new ThreadPoolExecutor(2,2,0L,TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>(10),new ThreadFactory(){
					@Override
					public Thread newThread(Runnable r) {
						Thread t=new Thread(r);
						t.setDaemon(true);
						System.out.println("create "+t);
						return t;
					}			
		});
		for(int i=0;i<20;i++){
			pool.execute(task);
			Thread.sleep(10);
		}
	}

}

可以看到默认的拒绝策略是抛出异常 

 

改大我们线程池的数量,设置线程数量为5,在自定义线程的时候,我们让创建出来的线程设置为守护线程 ,这样朱线程退出后,会销毁线程池。线程数量为5的时候的结果

 守护线程与用户线程的区别

守护线程, 是指在程序运行的时候在后台提供一种通用服务的线程, 比如垃圾回收线程就是一个很称职的守护者, 并且这种线程并不属于程序中不可或缺的部分. 因此, 当所有的非守护线程结束时, 程序也就终止了, 同时会杀死进程中的所有守护线程. 反过来说, 只要任何非守护线程还在运行, 程序就不会终止.

用户线程和守护线程两者几乎没有区别, 唯一的不同之处就在于虚拟机的离开: 如果用户线程已经全部退出运行了, 只剩下守护线程存在了, 虚拟机也就退出了.

猜你喜欢

转载自blog.csdn.net/wangdongli_1993/article/details/81253709
今日推荐