线程成本
首先我们需要了解线程是不是越多越好?
1、线程在Java中是一个对象,每一个Java线程都需要一个操作系统线程支持。线程创建、销毁需要时间。如果创建时间+销毁时间>执行任务时间就很不合算。
2、Java对象占用堆内存,操作系统线程占用系统内存,根据JVM规范,一个线程默认最大栈大小1M,这个栈空间是需要从系统内存中分配的。线程过多,会消耗很多的内存。
从上面我们知道,线程创建,销毁需要时间,线程创建需要占用系统内存。线程的存在是有成本的,这就要看这个成本会不会影响系统性能了。
线程池构成
线程池中有任务,有线程。线程会把任务放入线程中执行。
如上图,线程池会接收任务,将任务放入仓库中;然后线程会从仓库中取任务,把那个将任务运送至工作内存中执行。当没有任务时,线程阻塞,有任务时线程被唤醒。
自定义线程池
我们需要一个集合用于存放线程。
1 2
| //1. 多个线程组成 private List<Thread> workers;
|
然后再创建一个仓库,这个仓库是一个阻塞队列。
1 2
| //2.仓库 private BlockingQueue<Runnable> queue;
|
需要一个开关来控制线程。
1
| private volatile boolean isWorking = true;
|
编写线程类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| //poolSize: 工作线程数量 //taskSize: 任务数量 public FixedSizeThreadPool(int poolSize, int taskSize) {
if (poolSize <= 0 || taskSize <= 0) { throw new IllegalArgumentException("非法参数"); }
this.queue = new LinkedBlockingQueue<>(taskSize); //线程安全的list this.workers = Collections.synchronizedList(new ArrayList<>()); //创建线程,将线程放入集合中 for (int i = 0; i < poolSize; i++) { Worker worker = new Worker(this); worker.start(); workers.add(worker); } }
|
创建一个线程类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| //3.具体线程 public static class Worker extends Thread { private FixedSizeThreadPool pool;
public Worker(FixedSizeThreadPool pool) { this.pool = pool; }
@Override public void run() { while (pool.isWorking || pool.queue.size() > 0) { Runnable task = null;
try { if (pool.isWorking) { task = this.pool.queue.take();//阻塞方式拿,拿不到会一直等待 } else { //非阻塞操作 task = this.pool.queue.poll(); }
} catch (InterruptedException e) { e.printStackTrace(); }
if (task != null) { task.run(); System.out.println("线程:" + Thread.currentThread().getName() + "执行完毕");
} } } }
|
我们再编写一个执行任务的方法。
1 2 3 4 5 6 7 8
| public boolean execute(Runnable runnable) { if (isWorking) { //往阻塞队列中添加任务 return this.queue.offer(runnable); }
return false; }
|
关闭线程池。
1 2 3 4 5 6 7 8 9
| public void shutDown() { this.isWorking = false;
for(Thread thread : workers){ if (Thread.State.BLOCKED.equals(thread.getState())) { thread.interrupt();//中断线程 } } }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Test public void testPool() { int taskSize = 6; FixedSizeThreadPool fixedSizeThreadPool = new FixedSizeThreadPool(3, taskSize); for (int i = 0; i < taskSize; i++) { fixedSizeThreadPool.execute(() -> { System.out.println("任务被放入了仓库"); try { Thread.sleep(2000L); } catch (InterruptedException e) { System.out.println("线程中断"); } }); }
fixedSizeThreadPool.shutDown(); }
|
上面的代码并不完善,有兴趣的可以自行完善。项目地址FixedSizeThreadPool.java
线程池数量多少合适
如果是计算型任务?
cpu数量的1-2倍
如果是IO型任务?
则需多一些线程,要根据具体的IO阻塞时长进行考量决定。如tomcat中默认的最大线程数为:200
也可考虑根据需要在一个最小数量和最大数量间自动增减线程数。