线程池学习使用

线程池学习使用线程池学习使用

大家好,又见面了,我是你们的朋友全栈君。

文章转自:https://www.cnblogs.com/wihainan/p/4765862.html

在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

Java中可以通过线程池来达到这样的效果。

 

1 线程池做什么

 

网络请求通常有两种形式:

第一种,请求不是很频繁,而且每次连接后会保持相当一段时间来读数据或者写数据,最后断开,如文件下载,网络流媒体等。

另一种形式是请求频繁,但是连接上以后读/写很少量的数据就断开连接。考虑到服务的并发问题,如果每个请求来到以后服务都为它启动一个线程,那么这对服务的资源可能会造成很大的浪费,特别是第二种情况。

因为通常情况下,创建线程是需要一定的耗时的,设这个时间为T1,而连接后读/写服务的时间为T2,当T1>>T2时,我们就应当考虑一种策略或者机制来控制,使得服务对于第二种请求方式也能在较低的功耗下完成。

通常,我们可以用线程池来解决这个问题,首先,在服务启动的时候,我们可以启动好几个线程,并用一个容器(如线程池)来管理这些线程。

当请求到来时,可以从池中取一个线程出来,执行任务(通常是对请求的响应),当任务结束后,再将这个线程放入池中备用;

如果请求到来而池中没有空闲的线程,该请求需要排队等候。最后,当服务关闭时销毁该池即可。

 

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的

它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。

线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子:

假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,

而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。

所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。

 

合理利用线程池能够带来三个好处:

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

        但是要做到合理的利用线程池,必须对其原理了如指掌。

 

2 线程池的继承架构

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

 

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。

真正的线程池接口是ExecutorService。下面这张图完整描述了线程池的类体系结构。

 

Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;

 

然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;

抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;

然后ThreadPoolExecutor继承了类AbstractExecutorService。

 

标记一下比较重要的类:

ExecutorService

真正的线程池接口。

ScheduledExecutorService

能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

ThreadPoolExecutor

ExecutorService的默认实现。

ScheduledThreadPoolExecutor

继承ThreadPoolExecutorScheduledExecutorService接口实现,周期性任务调度的类实现。

 

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,

因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

 

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

                                           如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

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

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

newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,

                                       当任务数增加时,此线程池又可以智能的添加新线程来处理任务。

                                       此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

 

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。

它提供了如下方法来提交一个任务:

Future<?> submit(Runnable task)

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

 Callable 与 Runable的相关内容参见:

03 创建线程的第3式 

02 如何创建线程 线程并发与synchornized

 

3 使用线程池步骤及案例

线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

如何实现线程的代码呢?

A:创建一个线程池对象,控制要创建几个线程对象。

    public static ExecutorService newFixedThreadPool(int nThreads)

B:这种线程池的线程可以执行:

  可以执行Runnable对象或者Callable对象代表的线程

  做一个类实现Runnable接口。

C:调用如下方法即可

    Future<?> submit(Runnable task)

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

D:我就要结束,可以吗? 可以。

package xianCheng;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


class MyRunnable implements Runnable{

	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<50;i++){
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

public class ExecutorServiceDemo {
	public static void main(String args[]){
		ExecutorService pool=Executors.newFixedThreadPool(2);
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		pool.submit(new MyTask2());
		pool.shutdown();
	}

}

运行结果:

pool-1-thread-1:0

pool-1-thread-1:1

pool-1-thread-1:2

pool-1-thread-2:0

pool-1-thread-2:1

pool-1-thread-2:2

pool-1-thread-2:3

。。。

 

说明:

 

(1 newFixedThreadPool

是固定大小的线程池 有结果可见 我们指定2 在运行时就只有2个线程工作

若其中有一个线程异常  会有新的线程替代他

 

(2 shutdown方法有2个重载:

void shutdown() 启动一次顺序关闭,等待执行以前提交的任务完成,但不接受新任务。

List<Runnable> shutdownNow() 试图立即停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

 

(3 submit 与 execute

3.1 submit是ExecutorService中的方法 用以提交一个任务

他的返回值是future对象  可以获取执行结果

<T> Future<T> submit(Callable<T> task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。

Future<?> submit(Runnable task) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。

<T> Future<T> submit(Runnable task, T result) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。

 

3.2 execute是Executor接口的方法

他虽然也可以像submit那样让一个任务执行  但并不能有返回值

 

void execute(Runnable command)

在未来某个时间执行给定的命令。该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。

 

(4 Future

Future 表示异步计算的结果。

它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。

计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。

取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。

如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。

 

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。

必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。 

也就是说Future提供了三种功能:

–判断任务是否完成;

–能够中断任务;

–能够获取任务执行结果。

 

boolean cancel(boolean mayInterruptIfRunning) 试图取消对此任务的执行。

V get() 如有必要,等待计算完成,然后获取其结果。

V get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。

boolean isCancelled() 如果在任务正常完成前将其取消,则返回 true。

boolean isDone() 如果任务已完成,则返回 true。

 

4 线程池简单使用案例2

java.util.concurrent.Executors类的API提供大量创建连接池的静态方法:

package xianCheng;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class JavaThreadPool {
	public static void main(String args[]){
		ExecutorService pool=Executors.newFixedThreadPool(5);
		ExecutorService pool2=Executors.newFixedThreadPool(5);
		Thread t=new MyThread();
		Thread t1=new MyThread();
		Thread t2=new MyThread();
		Thread t3=new MyThread();
		Thread t4 =new MyThread();
		pool.execute(t4);
		pool.execute(t3);
		pool.execute(t2);
		pool.execute(t1);
		pool.execute(t);
		pool2.execute(t);
		
		pool.shutdown();
		pool2.shutdown();
		
	}
}
	class MyThread extends Thread{
		public void run(){
			System.out.println(Thread.currentThread().getName()+".....");
		}
	}

运行效果示例:

 

pool-1-thread-2正在执行… …

pool-1-thread-2正在执行… …

pool-1-thread-2正在执行… …

pool-1-thread-2正在执行… …

pool-1-thread-1正在执行… …

 

 

pool-1-thread-1正在执行… …

pool-1-thread-1正在执行… …

pool-1-thread-1正在执行… …

pool-1-thread-1正在执行… …

pool-1-thread-2正在执行… …

 

可见线程池中有2个线程在工作,可见 newFixedThreadPool 是固定大小的线程池

 

5 单任务线程池:

//创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

ExecutorService pool = Executors.newSingleThreadExecutor(); 

案例:

package xianCheng;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class SingleThreadPollDemo {
	public static void main(String args[]){
		ExecutorService pool=Executors.newSingleThreadExecutor();
		Runnable r1=new SingleTask();
		Runnable r2=new SingleTask();
		Runnable r3=new SingleTask();
		pool.execute(r1);
		pool.execute(r2);
		pool.execute(r3);
		pool.shutdown();
	}

}
class SingleTask implements Runnable{
	public void run(){
		System.out.println(Thread.currentThread().getName()+"正在执行。。。。");
		try{
			Thread.sleep(3000);
			
		}catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"执行完毕");
	}
}

运行结果:

 

pool-1-thread-1正在执行… …

pool-1-thread-1执行完毕

pool-1-thread-1正在执行… …

pool-1-thread-1执行完毕

pool-1-thread-1正在执行… …

pool-1-thread-1执行完毕

 

可见线程池中只有一个线程在执行任务

6 小结:

对于以上两种连接池,大小都是固定的,当要加入的池的线程(或者任务)超过池最大尺寸时候,则入此线程池需要排队等待。

一旦池中有线程完毕,则排队等待的某个线程会入池执行。

 

其他线程池示例:

 

固定大小线程池

import java.util.concurrent.Executors; 

import java.util.concurrent.ExecutorService;

ExecutorService pool = Executors.newFixedThreadPool(2);

pool.execute(t1);

pool.shutdown();

 

单任务线程池

ExecutorService pool = Executors.newSingleThreadExecutor();

 

可变尺寸线程池

ExecutorService pool = Executors.newCachedThreadPool();

 

延迟连接池

import java.util.concurrent.Executors; 

import java.util.concurrent.ScheduledExecutorService; 

import java.util.concurrent.TimeUnit;

ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);

pool.schedule(t4, 10, TimeUnit.MILLISECONDS);

 

单任务延迟连接池

ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();

 

7 使用示例

package xianCheng;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThread {
	public static void main(String args[]){
		ThreadPoolExecutor executor=new ThreadPoolExecutor(5, 10, 4000, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(6));
		for(int i=0;i<16;i++){
			MyTask task=new MyTask(i);
			executor.execute(task);
			System.out.println("线程池中线程数目:"+executor.getPoolSize()+"---队列中等待执行的线程数量:"+executor.getQueue().size()+"---已执行完的线程数目:"+executor.getCompletedTaskCount());
			
		}
		System.out.println("结束。。。");
		executor.shutdown();
	}
	
}
class MyTask implements Runnable{
	private int taskNum;
	public MyTask(int num){
		this.taskNum=num;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("正在执行task"+taskNum);
		try{
			Thread.currentThread().sleep(4000);
		}catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		System.out.println("task"+taskNum+"执行完毕");
	}
}

class MyTask2 extends Thread{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("正在执行task2");
	}
	
}

执行结果:

第一种情况,每个任务的线程等待时间是4s,for循环5次的结果:

正在执行task0

线程池中线程数目:1---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:2---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:3---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:4---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:0---已执行完的线程数目:0

正在执行task3

正在执行task4

正在执行task1

正在执行task2

task0执行完毕

task2执行完毕

task4执行完毕

task3执行完毕

task1执行完毕

结果说明,每进来一个任务线程池就创建一个线程,由于核心线程数量设置为5,所以进来的5个任务都得到了及时的处理,没有等待的线程。再由于等待时间过长,已完成的线程是0;

第二种情况,每个任务的线程等待时间是0s,for循环5次的结果:

 

正在执行task0

task0执行完毕

线程池中线程数目:1---队列中等待执行的线程数量:0---已执行完的线程数目:1

线程池中线程数目:2---队列中等待执行的线程数量:0---已执行完的线程数目:1

正在执行task1

task1执行完毕

线程池中线程数目:3---队列中等待执行的线程数量:0---已执行完的线程数目:1

正在执行task2

task2执行完毕

线程池中线程数目:4---队列中等待执行的线程数量:0---已执行完的线程数目:3

正在执行task3

task3执行完毕

线程池中线程数目:5---队列中等待执行的线程数量:0---已执行完的线程数目:4

正在执行task4

task4执行完毕

结果说明:由于不用等待,进来的线程任务及时得到处理,已执行完的线程数目有对应的数字,但是为啥一个线程已经执行完了,但是线程池的线程数目没有减少呢?因为线程池中创建的线程不会销毁,保持在核心线程个数量。知道线程池销毁才会消失。

第三种情况,每个任务的线程等待时间是2s,for循环15次的结果:

线程池中线程数目: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

正在执行task1

正在执行task0

正在执行task2

正在执行task10

正在执行task3

正在执行task11

正在执行task14

正在执行task12

正在执行task4

正在执行task13

task0执行完毕

task2执行完毕

task1执行完毕

正在执行task5

正在执行task6

正在执行task7

task3执行完毕

正在执行task8

task10执行完毕

task4执行完毕

task14执行完毕

task12执行完毕

task11执行完毕

正在执行task9

task13执行完毕

task5执行完毕

task6执行完毕

task7执行完毕

task8执行完毕

task9执行完毕

结果分析:可以看出这个线程池的最大处理线程的数量是10,当超过核心线程数5时,再进来的线程就要放入任务缓存队列里面了,最大的缓存数量是(最大线程数量-核心线程数量)。这里正在处理的线程数量也就是核心线程的数量5,等待的线程数量也就是10-5

 

第四种情况,每个任务的线程等待时间是0s,for循环15次的结果:

线程池中线程数目:1---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:2---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:3---队列中等待执行的线程数量:0---已执行完的线程数目:0

正在执行task2

线程池中线程数目:4---队列中等待执行的线程数量:0---已执行完的线程数目:0

正在执行task3

正在执行task0

线程池中线程数目:5---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:1---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:2---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:3---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:4---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:5---已执行完的线程数目:0

task2执行完毕

正在执行task5

task3执行完毕

正在执行task6

task5执行完毕

线程池中线程数目:6---队列中等待执行的线程数量:3---已执行完的线程数目:2

task0执行完毕

线程池中线程数目:6---队列中等待执行的线程数量:3---已执行完的线程数目:3

线程池中线程数目:6---队列中等待执行的线程数量:3---已执行完的线程数目:4

线程池中线程数目:6---队列中等待执行的线程数量:4---已执行完的线程数目:4

正在执行task1

线程池中线程数目:6---队列中等待执行的线程数量:5---已执行完的线程数目:4

正在执行task4

正在执行task10

task1执行完毕

正在执行task9

task6执行完毕

task9执行完毕

正在执行task12

task12执行完毕

正在执行task13

task13执行完毕

正在执行task14

task14执行完毕

正在执行task7

正在执行task11

task11执行完毕

task7执行完毕

task10执行完毕

task4执行完毕

正在执行task8

task8执行完毕

结果说明,这个没啥好说的。

 

第五种情况,每个任务的线程等待时间是4s,for循环16次的结果:

 

结果说明,可以看出报错了,因为进来的线程没有处理完,但是后面的任务又要进来,导致超出了线程池的最大线程数量,就报错了。

 

第六种情况,每个任务的线程等待时间是0s,for循环16次的结果:

线程池中线程数目:1---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:2---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:3---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:4---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:0---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:1---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:2---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:3---已执行完的线程数目:0

正在执行task3

线程池中线程数目:5---队列中等待执行的线程数量:4---已执行完的线程数目:0

线程池中线程数目:5---队列中等待执行的线程数量:5---已执行完的线程数目:0

task3执行完毕

正在执行task0

线程池中线程数目:6---队列中等待执行的线程数量:4---已执行完的线程数目:1

正在执行task5

线程池中线程数目:6---队列中等待执行的线程数量:5---已执行完的线程数目:1

线程池中线程数目:7---队列中等待执行的线程数量:5---已执行完的线程数目:1

线程池中线程数目:8---队列中等待执行的线程数量:5---已执行完的线程数目:1

task5执行完毕

正在执行task6

正在执行task13

task6执行完毕

线程池中线程数目:9---队列中等待执行的线程数量:4---已执行完的线程数目:2

正在执行task7

线程池中线程数目:9---队列中等待执行的线程数量:4---已执行完的线程数目:3

task7执行完毕

正在执行task8

task13执行完毕

正在执行task9

正在执行task1

task8执行完毕

正在执行task11

task9执行完毕

正在执行task15

task1执行完毕

正在执行task2

task11执行完毕

task15执行完毕

正在执行task4

task4执行完毕

正在执行task10

task0执行完毕

正在执行task12

正在执行task14

task12执行完毕

task10执行完毕

task2执行完毕

task14执行完毕

结果说明,每个任务得到了及时处理,没有什么问题。

那么怎么配置合适的核心线程数和最大线程数呢?在不考虑硬件因素的情况下。可以用loadrunner等测试工具测出,平均每个事务的处理时间是多长。然后估计系统数据访问量是多大,和每个访问最长能够等待的时间。比如说,我一次性进来17个访问量,处理每个事务的时间是4s,每个访问能够等待的时间是5s。那么 17/(4/5)=21.25  这里我们可以再上面案例这行代码中:

ThreadPoolExecutor executor=new ThreadPoolExecutor(5, 10, 4000, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(6));

10和6处代表的数字相加不大于21.25就行了。

另外,6处的数字代表队列中运行等待的线程数,可以理解为,当线程池中10个线程都已经被创建处理中,而在线程池的队列中有7任务在等待处理(并没有创建线程,因为这个线程池的最大承受线程数是10),也就是真正在被处理的线程是10个,有6个任务在等待(注意,不是线程在等待),

形如下面的代码:

package xianCheng;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThread {
	public static void main(String args[]){
		ThreadPoolExecutor executor=new ThreadPoolExecutor(5, 10, 4000, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(7));
		for(int i=0;i<18;i++){
			MyTask task=new MyTask(i);
			executor.execute(task);
			System.out.println("线程池中线程数目:"+executor.getPoolSize()+"---队列中等待执行的线程数量:"+executor.getQueue().size()+"---已执行完的线程数目:"+executor.getCompletedTaskCount());
			
		}
		System.out.println("结束。。。");
		executor.shutdown();
	}
	
}
class MyTask implements Runnable{
	private int taskNum;
	public MyTask(int num){
		this.taskNum=num;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("正在执行task"+taskNum);
		try{
			Thread.currentThread().sleep(4000);
		}catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		System.out.println("task"+taskNum+"执行完毕");
	}
}

class MyTask2 extends Thread{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("正在执行task2");
	}
	
}

线程池最大线程数+队列等待任务数=17;for循环的任务量是18,每个线程的处理时间是4s。这样就会报错,因为要处理的任务数超出了线程池的处理能力。如果将任务量改成17就刚好不会报错。看上面程序的输出结果:

线程池中线程数目:1---队列中等待执行的线程数量:0---已执行完的线程数目:0
线程池中线程数目:2---队列中等待执行的线程数量:0---已执行完的线程数目:0
正在执行task0
线程池中线程数目:3---队列中等待执行的线程数量:0---已执行完的线程数目:0
正在执行task2
线程池中线程数目:4---队列中等待执行的线程数量:0---已执行完的线程数目:0
正在执行task1
正在执行task3
线程池中线程数目:5---队列中等待执行的线程数量:0---已执行完的线程数目:0
正在执行task4
线程池中线程数目:5---队列中等待执行的线程数量:1---已执行完的线程数目:0
线程池中线程数目:5---队列中等待执行的线程数量:2---已执行完的线程数目:0
线程池中线程数目:5---队列中等待执行的线程数量:3---已执行完的线程数目:0
线程池中线程数目:5---队列中等待执行的线程数量:4---已执行完的线程数目:0
线程池中线程数目:5---队列中等待执行的线程数量:5---已执行完的线程数目:0
线程池中线程数目:5---队列中等待执行的线程数量:6---已执行完的线程数目:0
线程池中线程数目:5---队列中等待执行的线程数量:7---已执行完的线程数目:0
线程池中线程数目:6---队列中等待执行的线程数量:7---已执行完的线程数目:0
正在执行task12
正在执行task13
线程池中线程数目:7---队列中等待执行的线程数量:7---已执行完的线程数目:0
线程池中线程数目:8---队列中等待执行的线程数量:7---已执行完的线程数目:0
正在执行task14
线程池中线程数目:9---队列中等待执行的线程数量:7---已执行完的线程数目:0
正在执行task15
线程池中线程数目:10---队列中等待执行的线程数量:7---已执行完的线程数目:0
正在执行task16
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task xianCheng.MyTask@232204a1 rejected from java.util.concurrent.ThreadPoolExecutor@4aa298b7[Running, pool size = 10, active threads = 10, queued tasks = 7, 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 xianCheng.TestThread.main(TestThread.java:12)
task0执行完毕
正在执行task5
task3执行完毕
正在执行task6
task2执行完毕
正在执行task7
task1执行完毕
正在执行task8
task13执行完毕
正在执行task9
task4执行完毕
task12执行完毕
正在执行task11
正在执行task10
task16执行完毕
task14执行完毕
task15执行完毕
task5执行完毕
task7执行完毕
task6执行完毕
task8执行完毕
task9执行完毕
task10执行完毕
task11执行完毕

从执行结果可以看出,只有前17(这里是从0计算的)个任务是即是报错也被执行了,因为是异步的。但是第18个任务并没有被执行。

如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。

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

1 Executors.newCachedThreadPool();        //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE2 Executors.newSingleThreadExecutor();    //创建容量为1的缓冲池3 Executors.newFixedThreadPool(int);      //创建固定容量大小的缓冲池

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

 1 public static ExecutorService newFixedThreadPool(int nThreads) {
 2     return new ThreadPoolExecutor(nThreads, nThreads, 3                                   0L, TimeUnit.MILLISECONDS, 4                                   new LinkedBlockingQueue<Runnable>()); 5 } 6 public static ExecutorService newSingleThreadExecutor() {
 7     return new FinalizableDelegatedExecutorService 8         (new ThreadPoolExecutor(1, 1, 9                                 0L, TimeUnit.MILLISECONDS,10                                 new LinkedBlockingQueue<Runnable>()));11 }12 public static ExecutorService newCachedThreadPool() {
13     return new ThreadPoolExecutor(0, Integer.MAX_VALUE,14                                   60L, TimeUnit.SECONDS,15                                   new SynchronousQueue<Runnable>());16 }

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

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

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

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

  实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

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

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/106183.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 查看端口常用命令以及关闭端口的方法

    查看端口常用命令以及关闭端口的方法dos命令行查询端口常用命令在windows命令行窗口下执行:运行–cmd–netstat-a显示所有活动的TCP连接以及计算机监听的TCP和UDP端口。netstat-e显示以太网发送和接收的字节数、数据包数等。netstat-n以数字形式显示所有活动的TCP连接的地址和端口号。netstat-o显示活动的TCP连接并包括每个连接的进程ID(PID)。netstat-s按协议显示

  • git clone失败或连接超时解决方案「建议收藏」

    git clone失败或连接超时解决方案「建议收藏」gitclone失败或连接超时解决方案当我们在git上克隆代码时,可能会报错或者连接超时:如下图可以把仓库地址前缀https换成git:gitclonehttps://github.com/coderwhy/supermall.gitgitclonegit://github.com/coderwhy/supermall.git2.克隆成功…

  • 互联网金融风控模型「建议收藏」

    互联网金融风控模型「建议收藏」一、市场调研目前市面主流的风控模型1、互联网金融前10名排行榜(数据截止日期2017-09-12)互联网金融公司排名分别是蚂蚁金服、陆金所、京东金融、苏宁金融、百度金融、腾讯理财通、宜信、钱大掌柜、万达金融和网易理财。1.1蚂蚁金服1.1.1大数据技术对接第三方征信公司芝麻信用分,通过用户信用历史、行为偏好、履约能力、身份特质、人脉关系五个维度对…

  • 关于使用LayoutParams清除设置以及DateFormat无法正确转换格式化日期的问题

    关于使用LayoutParams清除设置以及DateFormat无法正确转换格式化日期的问题1、关于LayoutParams清除设置问题RelativeLayout.LayoutParamslp=(LayoutParams)mBtn.getLayoutParams();lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,0);//清除上次设置(只有清除上次设置,这次设置才会起效,倘若代码设置过后不需要再次更改布局,则无须清除上次设置)

  • androidrepublic_android develop

    androidrepublic_android developPreferenceFragment用来显示首选项的设置,效果图如下:主布局文件:<RelativeLayoutxmlns:android=”http://schemas.android.com/apk/res/android”xmlns:tools=”http://schemas.android.com/tools”android:…

  • 计算机组成原理之指令周期[通俗易懂]

    计算机组成原理之指令周期[通俗易懂]指令周期基本概念基本概念指令周期取出并执行一条指令所需的全部时间每条指令的周期不同由于各指令的功能不同,他们的周期也是不尽相同的。eg:NOP(空操作):只有取指周期ADD(加操作):指令周期有取指周期和执行周期两个阶段具有间址寻址的指令周期取指周期:把指令地址从内存中取出间址周期:把操作数地址从内存中取出执行周期:把操作数从内存中取出指令周期流程…

    2022年10月13日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号