线程池的应用

1、线程池状态

在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile int runState;
static final int RUNNING    = 0;
static final int SHUTDOWN   = 1;
static final int STOP       = 2;
static final int TERMINATED = 3;

runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
当创建线程池后,初始时,线程池处于RUNNING状态
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

2、任务的执行

线程池的应用_第1张图片

 每个变量的作用都已经标明出来了,这里要重点解释一下corePoolSize、maximumPoolSize、largestPoolSize三个变量。

   corePoolSize:在很多地方被翻译成核心池大小,其实我的理解这个就是线程池的大小。举个简单的例子(转载的):

  假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;然后就将任务也分配给这4个临时工人做;如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请  额外的工人是要花钱的。

这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。不过为了方便理解,在本文后面还是将corePoolSize翻译成核心池大小。largestPoolSize只是一个用来起记录作用的变量,用来记录线程池中曾经有过的最大线程数目,跟线程池的容量没有任何关系。

使用有界队列时:ArrayBlockingQueue
    若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
    若大于corePoolSize,则会将任务加入队列,
    若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
    若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式。

使用无界队列时:LinkedBlockingQueue
    若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
    若线程数达到corePoolSize则放在无界队列里,maximumPoolSize不生效
    

应用示例:

class MyTask implements Runnable {
    private int taskNum;
     
    public MyTask(int num) {
        this.taskNum = num;
    }
     
    @Override
    public void run() {
        System.out.println("正在执行task "+taskNum);
        try {
            Thread.currentThread().sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"执行完毕");
    }
}
public class Test {
     public static void main(String[] args) {   
         ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue(5));
          
         for(int i=0;i<18;i++){
             MyTask myTask = new MyTask(i);
             executor.execute(myTask);
             System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
         }
         executor.shutdown();
     }
}

执行结果

线程池中线程数目:1,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:4,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:1,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:2,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:3,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:4,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:6,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:7,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:8,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:9,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.emax.paycenter.task.MyTask@6ce2e687 rejected from java.util.concurrent.ThreadPoolExecutor@1dd1702e[Running, pool size = 10, active threads = 10, queued tasks = 5, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
	at com.emax.paycenter.task.RateLimiterDemo.main(RateLimiterDemo.java:20)
正在执行task 0
正在执行task 1
正在执行task 2
正在执行task 3
正在执行task 4
正在执行task 10
正在执行task 11
正在执行task 12
正在执行task 13
正在执行task 14
task 0执行完毕
正在执行task 5
task 1执行完毕
正在执行task 6
task 2执行完毕
正在执行task 7
task 3执行完毕
正在执行task 8
task 10执行完毕
正在执行task 9
task 4执行完毕
task 11执行完毕
task 12执行完毕
task 13执行完毕
task 14执行完毕
task 5执行完毕
task 6执行完毕
task 7执行完毕
task 9执行完毕
task 8执行完毕

从执行结果可看出,当线程池中线程的数目大于5时,便将任务放入任务缓存队列里,当任务缓存队列满了之后,便创建新的线程(不能超过10)。如果上面程序中,将for循环中改成执行大于15个任务,就会抛出任务拒绝异常了,任务最多执行的的个数为 maximumPoolSize + new ArrayBlockingQueue(5)  即 10+5 = 15  超过会报以下错误

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task ne.MyTask@1ce3fc5 rejected from java.util.concurrent.ThreadPoolExecutor@1bcdbf6[Running, pool size = 11, active threads = 11, queued tasks = 6, completed tasks = 0]

     at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2001)

     at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:816)

     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1337)

     at ne.Test.main(Test.java:13)

 

不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:(线程池创建的四种方式)

(但是阿里巴巴Java开发手册中明确指出,而且用的词是『不允许』使用Executors创建线程池

Executors.newSingleThreadExecutor();        //单个线程的线程池,线程中只有一个线程工作,单线程串行执行任务
Executors.newCachedThreadPool();            //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE,来了任务就创建线程运行,当线程空闲超过60s,就会销毁线程
Executors.newFixedThreadPool(int);          //创建固定容量大小的缓冲池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后进入后面的等待队列,直到前面的线程执行完毕
Executors.newScheduleThreadExecutor(int );   //大小无限制的线程池,支持定时和周期性地执行线程
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
}

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()));
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue());
}

常见线程池

class MyRunnable implements Runnable{
     
     private int num;
     
     public MyRunnable(int num) {
          super();
          this.num = num;
     }
     @Override
     public void run() {
          System.out.println(num);
     }
}

①newSingleThreadExecutor

public class MyTest {
	public static void main(String[] args) throws InterruptedException {
		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10000; i++) {
			Thread.sleep(10);
			singleThreadExecutor.execute(new MyRunnable(i));
		}
	}
}

②newFixedThreadExecutor(n)

public class MyTest {
	public static void main(String[] args) throws InterruptedException {
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
		for (int i = 0; i < 10000; i++) {
			fixedThreadPool.execute(new MyRunnable(i));
		}
	}
}

③newCacheThreadExecutor

public class MyTest {
	public static void main(String[] args) throws InterruptedException {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		for (int i = 0; i < 10000; i++) {
			cachedThreadPool.execute(new MyRunnable(i));
		}

	}
}

④newScheduleThreadExecutor(n)

  ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        ① scheduledThreadPool.scheduleAtFixedRate(new MyRunnable(1), 1, 3, TimeUnit.SECONDS);
		使用scheduleAtFixedRate , 该方法第三个参数表示在上一个个任务开始执行之后延迟多少秒之后再执行, 是从上一个任务开始时开始计算 
        ② scheduledThreadPool.scheduleWithFixedDelay(new MyRunnable(1), 1, 3, TimeUnit.SECONDS);
		该方法第三个参数表示在上一个个任务结束执行之后延迟多少秒之后再执行, 是从上一个任务结束时开始计算

  第二个参数代表多久后开始执行线程
public class MyRunnable implements Runnable {

	int sleepTime;

	public MyRunnable(int sleepTime) {
		this.sleepTime = sleepTime;
	}

	@Override
	public void run() {
		long start = new Date().getTime();
		System.out.println("scheduleAtFixedRate 开始执行时间:" +
				DateFormat.getTimeInstance().format(new Date()));
		try {
			Thread.sleep(sleepTime);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = new Date().getTime();
		System.out.println("scheduleAtFixedRate 执行花费时间=" + (end - start) / 1000 + "m");
		System.out.println("scheduleAtFixedRate 执行完成时间:"
				+ DateFormat.getTimeInstance().format(new Date()));
		System.out.println("======================================");
	}

}
/**
 * Created by wangxiaodong on 2018/5/30.
 * 此线程池支持定时以及周期性执行任务的需求
 */
public class TestNewScheduledThreadPool {
	public static void main(String[] args) {

		ScheduledExecutorService service = Executors.newScheduledThreadPool(2);

		//试验1
 		service.scheduleAtFixedRate(new MyRunnable(2000), 1000, 5000, TimeUnit.MILLISECONDS);

		//试验2
		service.scheduleAtFixedRate(new MyRunnable(7000), 1000, 5000, TimeUnit.MILLISECONDS);

		//试验3
 		service.scheduleWithFixedDelay(new MyRunnable(2000), 1000, 5000, TimeUnit.MILLISECONDS);

		//试验4
		service.scheduleWithFixedDelay(new MyRunnable(7000), 1000, 5000, TimeUnit.MILLISECONDS);
	}
}

试验1

scheduleAtFixedRate 开始执行时间:14:21:02
scheduleAtFixedRate 执行花费时间=2m
scheduleAtFixedRate 执行完成时间:14:21:04
======================================
scheduleAtFixedRate 开始执行时间:14:21:07
scheduleAtFixedRate 执行花费时间=2m
scheduleAtFixedRate 执行完成时间:14:21:09
======================================
scheduleAtFixedRate 开始执行时间:14:21:12
scheduleAtFixedRate 执行花费时间=2m
scheduleAtFixedRate 执行完成时间:14:21:14
======================================
scheduleAtFixedRate 开始执行时间:14:21:17
scheduleAtFixedRate 执行花费时间=2m
scheduleAtFixedRate 执行完成时间:14:21:19
======================================

分析得出:在任务执行时间小于间隔时间的情况下,程序以起始时间为准则,每隔指定时间执行一次,不受任务执行时间影响

试验2

scheduleAtFixedRate 开始执行时间:14:22:52
scheduleAtFixedRate 执行花费时间=7m
scheduleAtFixedRate 执行完成时间:14:22:59
======================================
scheduleAtFixedRate 开始执行时间:14:22:59
scheduleAtFixedRate 执行花费时间=7m
scheduleAtFixedRate 执行完成时间:14:23:06
======================================
scheduleAtFixedRate 开始执行时间:14:23:06
scheduleAtFixedRate 执行花费时间=7m
scheduleAtFixedRate 执行完成时间:14:23:13
======================================

分析得出:当执行任务时间大于间隔时间,此方法不会重新开启一个新的任务进行执行,而是等待原有任务执行完成,马上开启下一个任务进行执行。此时,执行间隔时间已经被打乱。

试验3

scheduleAtFixedRate 开始执行时间:14:23:43
scheduleAtFixedRate 执行花费时间=2m
scheduleAtFixedRate 执行完成时间:14:23:45
======================================
scheduleAtFixedRate 开始执行时间:14:23:50
scheduleAtFixedRate 执行花费时间=2m
scheduleAtFixedRate 执行完成时间:14:23:52
======================================
scheduleAtFixedRate 开始执行时间:14:23:57
scheduleAtFixedRate 执行花费时间=2m
scheduleAtFixedRate 执行完成时间:14:23:59
======================================

分析得出:当执行任务小于延迟时间时,第一个任务执行之后,延迟指定间隔时间,然后开始执行第二个任务。

试验4

scheduleAtFixedRate 开始执行时间:14:24:49
scheduleAtFixedRate 执行花费时间=7m
scheduleAtFixedRate 执行完成时间:14:24:56
======================================
scheduleAtFixedRate 开始执行时间:14:25:01
scheduleAtFixedRate 执行花费时间=7m
scheduleAtFixedRate 执行完成时间:14:25:08
======================================
scheduleAtFixedRate 开始执行时间:14:25:13
scheduleAtFixedRate 执行花费时间=7m
scheduleAtFixedRate 执行完成时间:14:25:20

得出结论:当执行任务大于延迟时间时,第一个任务执行之后,延迟指定间隔时间,然后开始执行第二个任务。

此方法无论任务执行时间长短,都是当第一个任务执行完成之后,延迟指定时间再开始执行第二个任务。

参考资料:http://blog.csdn.net/xieyuooo/article/details/8718741

但是阿里巴巴Java开发手册中明确指出,而且用的词是『不允许』使用Executors创建线程池

【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 
说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

根据阿里巴巴java开发规范,推荐了3种线程池创建方式    

推荐方式1:首先引入:commons-lang3包

  ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

推荐方式 2:首先引入:com.google.guava包

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
 
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
 
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown

推荐方式 3:spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean


    
    
    
    
    
    
    
    
 
    
    
        
            
    
userThreadPool.execute(thread);

 

你可能感兴趣的:(多线程)