线程成本

首先我们需要了解线程是不是越多越好?

1、线程在Java中是一个对象,每一个Java线程都需要一个操作系统线程支持。线程创建、销毁需要时间。如果创建时间+销毁时间>执行任务时间就很不合算。

2、Java对象占用堆内存,操作系统线程占用系统内存,根据JVM规范,一个线程默认最大栈大小1M,这个栈空间是需要从系统内存中分配的。线程过多,会消耗很多的内存。

从上面我们知道,线程创建,销毁需要时间,线程创建需要占用系统内存。线程的存在是有成本的,这就要看这个成本会不会影响系统性能了。

线程池构成

线程池中有任务,有线程。线程会把任务放入线程中执行。

手写线程池_java


如上图,线程池会接收任务,将任务放入仓库中;然后线程会从仓库中取任务,把那个将任务运送至工作内存中执行。当没有任务时,线程阻塞,有任务时线程被唤醒。

自定义线程池

我们需要一个集合用于存放线程。

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
也可考虑根据需要在一个最小数量和最大数量间自动增减线程数。