Java中有几种线程池?使用线程池有什么风险?

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

使用线程池能够为了防止资源不足,因为频繁创建和销毁线程会消耗大量资源,尤其是当线程执行时间>线程创建时间+线程销毁时间,此时会堆积大量线程。Java中,创建线程池有四种方式,如下:

1)newCachedThreadPool()
创建一个可缓存线程池,线程池为无限大,如果线程池长度超过处理需要,可灵活回收空闲线程,即当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程;如果上一个线程没有结束则会新建线程。

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

public class CachedThreadPool {
	private static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

	public static void main(String args[]) {
		for (int i = 0; i < 10; i++) {
			try {
				if (i == 4) {
					Thread.sleep(2000);
				}
			} catch (Exception e) {
				e.getStackTrace();
			}
			cachedThreadPool.execute(new Runnable() {
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						e.getStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在被执行");
				}
			});
		}
	}
	/**
	 * 执行结果如下(console输出): 
	 * pool-1-thread-1正在被执行 
	 * pool-1-thread-4正在被执行
	 * pool-1-thread-3正在被执行 
	 * pool-1-thread-2正在被执行 
	 * pool-1-thread-4正在被执行
	 * pool-1-thread-1正在被执行 
	 * pool-1-thread-3正在被执行 
	 * pool-1-thread-2正在被执行
	 * pool-1-thread-5正在被执行 
	 * pool-1-thread-6正在被执行
	 */
}

在i=5的时候,需停留2秒,此时线程1-4都已经释放,所以1-4线程可以复用,这就是newCachedThreadPool()线程池的优点。但是这样的写法也会带来一个缺点,就是一旦线程无限增长,会导致内存溢出。

2)newFixedThreadPool()
可重用固定个数的线程池,当当前线程数大于总线程数时会进行等待,等待线程池内的线程执行完,相对来说占用内存少,因为如果等待线程数量过多,也是相对耗废资源的。

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

public class FixedThreadPool {
	private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);

	public static void main(String args[]) {
		for (int i = 0; i < 10; i++) {
			fixedThreadPool.execute(new Runnable() {
				public void run() {
					System.out.println(Thread.currentThread().getName() + "正在被执行");
					try {
						Thread.sleep(1000);
					} catch (Exception e) {
						e.getStackTrace();
					}
				}
			});
		}
	}
}

线程池大小为4,所以如果线程执行数量超过4则会进行等待,所以newFixedThreadPool()的缺点也很明显,就是如果线程池数量设置明显过小,则会导致大量线程等待,造成资源浪费。所以一句话总结,缺点是:不支持自定义拒绝策略,大小固定,难以扩展。

3)newSingleThreadExecutor()
单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行,如果该线程异常结束,会有另一个线程取代它,保证顺序执行。

所以缺点也很明显,就是单线程执行,线程多,执行速度会比较慢,所以不适合并发。

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

public class SingleThreadPool {
	private static ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

	public static void main(String args[]) {
		for (int i = 0; i < 10; i++) {
			final int index = i;
			singleThreadPool.execute(new Runnable() {
				public void run() {
					try {
						System.out.println(Thread.currentThread().getName() + "正在执行 index=" + index);
						Thread.sleep(1000);
					} catch (Exception e) {
						e.getStackTrace();
					}
				}
			});
		}
	}
	/**
	 * 执行结果如下: 
	 * pool-1-thread-1正在执行 index=0 
	 * pool-1-thread-1正在执行 index=1
	 * pool-1-thread-1正在执行 index=2 
	 * pool-1-thread-1正在执行 index=3
	 * pool-1-thread-1正在执行 index=4 
	 * pool-1-thread-1正在执行 index=5
	 * pool-1-thread-1正在执行 index=6 
	 * pool-1-thread-1正在执行 index=7
	 * pool-1-thread-1正在执行 index=8 
	 * pool-1-thread-1正在执行 index=9
	 * 
	 * 结论:所有的线程都是线程1来执行而且是有序执行,这就是单线程池!
	 */
}

4)newScheduledThreadPool()
可重用固定个数的线程池,当前线程数大于总数则会进行等待,并且可以设置线程延迟执行时间。

优点:一个固定大小线程池,可以定时或周期性的执行任务

缺点:任务是单线程方式执行,一旦一个任务失败其他任务也受影响

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPool {
	private static ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);

	public static void main(String args[]) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmsssss");

		for (int i = 0; i < 10; i++) {
			System.out.println(i + "开始执行时间" + sdf.format(new Date()));
			scheduledThreadPool.schedule(new Runnable() {
				public void run() {
					System.out.println(Thread.currentThread().getName() + "正在执行 执行时间=" + sdf.format(new Date()));
				}
			}, 3, TimeUnit.SECONDS);
		}
	}
}

那么,使用线程池会造成什么风险呢?

1)死锁:任何的多线程都有可能发生死锁的风险(线程之间互相等待)

2)资源不足:线程池太大造成,正常来说合理创建线程池大小一般不会出现这个问题

3)线程泄漏和请求过载

>>>>>阿里巴巴开发规范<<<<<
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool 和 newScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

所以,按照阿里巴巴的开发规范,实际编程中我们需要使用ThreadPoolExecutor创建线程池,它集以上优点于一身,比如:

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

public class ThreadPoolExecutorTest {

	public static void main(String[] args) {
		ThreadPoolExecutor executor = new ThreadPoolExecutor(1, // corePoolSize
				100, // maximumPoolSize
				100, // keepAliveTime
				TimeUnit.SECONDS, // unit
				new LinkedBlockingDeque<>(100));// workQueue

		for (int i = 0; i < 5; i++) {
			final int taskIndex = i;
			executor.execute(() -> {
				System.out.println(taskIndex);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}

	}

}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler);

参数解释:

  corePoolSize : 线程池核心池的大小。

  maximumPoolSize : 线程池的最大线程数。

  keepAliveTime : 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

  unit : keepAliveTime 的时间单位。

  workQueue : 用来储存等待执行任务的队列。

  threadFactory : 线程工厂。

  handler  拒绝策略。

原理:

  有请求时,创建线程执行任务,当线程数量等于corePoolSize时,请求加入阻塞队列里,当队列满了时,接着创建线程,线程数等于maximumPoolSize。 当任务处理不过来的时候,线程池开始执行拒绝策略。

  阻塞队列:

  ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。

  LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。

  PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。

  DelayQueue: 一个使用优先级队列实现的无界阻塞队列。

  SynchronousQueue: 一个不存储元素的阻塞队列。

  LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。

  LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。

  拒绝策略:

  ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 (默认)

  ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

  ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务。(重复此过程)

  ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值