关于多线程和线程安全相关讨论(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bloodylzy/article/details/79140363

三:线程池的相关总结

 

1.线程池的意义:

线程虽然好用,但是如果创建了大量的线程,会拖垮整个程序,甚至可能会出现OOM异常;另一方面,大量的线程被GC的时候,也会产生巨大的压力,延长GC的停顿时间。

其次,线程在被创建和销毁的时候,也是会消耗系统的内存和资源的。

说的简单一点,你们JDBC连接是不是用连接池的,现在基本上不会有人去创建JDBC连接,然后去使用它了,基本都是利用连接池,取得池内的连接。线程池也是一样的概念。我们不再创建线程,而是从线程池中去取得,由线程池去控制和管理线程。


2.最基本的线程池:

JDK对于多线程的控制,专门提供了一套Executor框架。java.util.concurrentjava的并发包。其中Executors类是线程池工厂,通过Executors类我们可以得到不同功能的线程池。

1)newFixedThreadPool:构造方法时必须传入线程数(Int),这是一个固定传入参数的线程数的线程池

2)newSingleThreadPool:这是一个固定只有一个线程的线程池

3)newCacheThreadPool:这是一个最大线程数无上限的线程池

这三个线程池,看似功能不同,但是其实他们都是ThreadPoolExecutor类的封装,我们可以看一下,关于这个ThreadPoolExecutor这个类的构造方法;

扫描二维码关注公众号,回复: 2963680 查看本文章

最完整的构造方法里面构造参数有7个,分别是corePoolSizemaximumPoolSizekeepActiveTimeunitworkQueuethreadFactoryRejectedExecutionHandler

corePoolSize 表示线程池的核心线程数,

maximumPoolSize 表示线程池最大线程数

keepActiveTime 表示在线程池中,空闲线程的存活时间

Unit 表示上面存活时间的时间单位

workQueue 表示线程池中的任务队列

threadFactory 表示线程池中的线程工厂,基本上都是选择默认的,关于这个东西,阿里云的代码规范对这个线程工厂做了一个补充,大家可以去看看

RejectedExecutionHandler 表示的是这个线程池中的拒绝策略,JDK默认的拒绝策略是AbortPolicy

我觉得知道了连接池,再去看线程池,其实都是差不多的,所以什么核心线程数,最大线程数,存活时间的我就不多说了,觉得可以说一下的是workQueueRejectedExecutionHandler

 

一般来说,任务队列(workQueue)有以下的几种,

1.SynchronousQueue(直接提交队列),这个任务队列,他没有容量,基本就是属于有了任务,就将任务提交给线程池,让线程池创建新的线程,直至线程池中的活跃线程大于最大线程数,这个时候,线程池就直接执行拒绝策略(RejectedExecutionHandler

2.ArrayBlockingQueue(有界队列),如果要使用这个任务队列,就必须在初始化的时候,传入容量参数,代表这个任务队列的最大容量。对于这个任务队列而言,当线程池中的活跃线程数等于核心线程数之后,这个队列就不提交任务了,直到任务堆积过多,超过了容量参数,这个时候,任务队列才会继续提交任务,让线程池开始生成新的线程,直到活跃线程数大于最大线程数,而这个时候,线程池就开始执行拒绝策略了

3.LinkedBlockingQueue(无界队列),这个队列,他的容量是无限的,他可以不断的增长,直到吃完系统的所有资源。对于这个队列而言,当线程池中的活跃线程数等于核心线程数之后,他也同样不会提交任务,但是和有界队列不同,因为他的容量是无限的,所以不存在会使用最大线程数的时候,也不存在拒绝策略,除非你的系统先挂掉

4.PriorityBlockingQueue(优先队列),这个队列,是一个特殊的无界队列,所以在对待线程池内的线程数的控制方面,是和无界队列一致的。对于有界队列和无界队列而言,他们其实都是按照先进先算,后进后算的方法来向线程池传递任务的。但是优先队列正如名字一样,他是会根据任务自己的优先级来决定先后执行顺序的。所以换句话说,如果你要使用优先队列,那么就要让你的任务去实现Comparable接口,重写里面的compareTo方法,具体可以参考下面的代码

package com.liu.javathread.priorityblockingqueue;

public class MyThread implements Runnable, Comparable<MyThread>{

	protected String name;

	public MyThread(String name) {
		super();
		this.name = name;
	}

	public MyThread() {
		
	}

	@Override
	public int compareTo(MyThread o) {
		// 这是自身这个线程的数值
		int me = Integer.parseInt(this.name.split("_")[1]);
		// 传参的线程的数据
		int other = Integer.parseInt(o.name.split("_")[1]);
		if(me > other){
			return 1;
		}else if(me < other){
			return -1;
		}else{
			return 0;
		}
	}

	@Override
	public void run() {
		try {
			// 系统沉睡模拟工作任务
			Thread.sleep(1000);
			System.out.println(name+" ");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	
	
}
package com.liu.javathread.priorityblockingqueue;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadMain {

	public static void main(String[] args) {
		
		ExecutorService executor = new ThreadPoolExecutor(10, 100, 2, TimeUnit.SECONDS, new PriorityBlockingQueue<Runnable>());
		for(int i=0;i<1000;i++){
			executor.execute(new MyThread("testThread_"+Integer.toString(999-i)));
		}
	}

}

而关于拒绝策略,我觉得一般来说,选择默认的AbortPolicy就可以了,这个拒绝策略意思是直接抛出异常,阻止系统继续工作

另外的拒绝策略,CallerRunsPolicyDiscardOledestPolicy,DiscardPolicy这些策略,大家有兴趣可以自己研究一下,另外如果说,你对于这些拒绝策略都不满意,可以自己去重新实现。RejectedExecutionHandler 这个东西其实他是一个接口,上述的那些都是他的实现。

最后大家可以去看一下,之前我们说的三个JDK定义好的线程池,他们的核心线程数,最大线程数,存活时间,包括任务队列分别都是些什么。这对于我们已经选择JDK自带的线程池有很大的帮助


3.扩展线程池:

阿里巴巴的代码规范中,建议我们使用线程池,而且最好不是使用JDK自带的线程池,而是利用最基本的线程池ThreadPoolExecutor,不仅仅是因为我们可以自由的选择任务队列,拒绝策略,更多的是因为,这个线程池具有可扩展性

首先具体的来说,最具代表性的就是beforeExecutor()afterExecutor()这两个方法,这两个方法看方法名就知道,他们分别是在线程池内线程启动之前和线程结束之后分别去调用的。

我们可以通过继承ThreadPoolExecutor这个类,然后重写这两个方法,去实现我们的业务,我个人觉得这个在调试的时候比较有用,至少你可以知道你的多个线程在执行前和执行后的状态。具体可以参考MyThreadPool这个文件。另外如果大家有兴趣的话,可以去找一下为什么线程池内部在调用工作线程的时候,会调用这两个方法

 

猜你喜欢

转载自blog.csdn.net/bloodylzy/article/details/79140363