JavaWeb笔记09:线程池

文章目录

    • 线程池(thread pool)
        • 0 线程池的使用场景
        • 1 为什么要用线程池:
        • 2 没有线程池的情况下:
        • 3 加入线程池之后:
        • 4 JDK创建线程的方式:固定长度的线程池和临时线程池
          • 1) 调用固定长度的线程池`Executors.newFixedThreadPool(10):`
          • 2)调用临时线程池`Executors.newCachedThreadPool()`:
        • 5 ThreadPoolExecutor类是一个线程池类:
        • 6 拒绝任务的策略:
        • 7 线程池处理任务的流程:
          • 1)ThreadPoolExecutor类的使用:
          • 2)自定义一个固定长度的线程池:

线程池(thread pool)

线程池作用就是限制系统中执行线程的数量。 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果。

0 线程池的使用场景

  • 单个任务处理的时间比较短
  • 需处理的任务的数量大

1 为什么要用线程池:

  • 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  • 可以根据系统的承受能力,调整线程池中工作线程的数目,防止内存消耗过多。

2 没有线程池的情况下:

import java.util.Scanner;

public class NoThreadPool {
    static class Deliver extends Thread {
        String task;

        public Deliver(String task) {
            this.task = task;
        }

        @Override
        public void run() {
            System.out.println("开始进行任务: " + task);
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务完成: " + task);
        }
    }
    
    public static void main(String[] args) {
        // 主线程就是前台
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String task = scanner.nextLine();
            Thread worker = new Deliver(task);
            worker.start();
        }
    }
}

没有线程池时,每次执行任务,都要重新开辟一个线程,造成资源浪费。

3 加入线程池之后:

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

public class HasThreadPool {
    static class Tasks implements Runnable {
        String task;

        public Tasks(String task) {
            this.task = task;
        }

        @Override
        public void run() {
            System.out.println("开始执行任务: " + task);
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务结束: " + task);
        }
    }

    public static void main(String[] args) {
        // 容纳了10个线程的线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        // 主线程就是前台
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String thing = scanner.nextLine();
            Tasks task = new Tasks(thing);
            pool.execute(task);
        }
    }
}

注意:
线程池,一开始不会创建好所有的线程。随着线程任务被提交,他会判断当前需要开启线程数是否小于正式线程数,如果小于,则创建线程。随着每加一个任务,就会随之创建一个线程,当达到正式线程数时就不会再创建了。线程创建好后,如果没有任务就会等待任务到来。当线程处理不了当前任务时,则将任务放到阻塞队列中等待空闲线程来取。

4 JDK创建线程的方式:固定长度的线程池和临时线程池

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

public class SomeSimpleThreadPool {
    public static void main(String[] args) {
        ExecutorService p1 = Executors.newFixedThreadPool(10);
        ExecutorService p2 = Executors.newCachedThreadPool();
    }
}

固定长度的线程池和临时线程池返回的都是一个ThreadPoolExecutor类

1) 调用固定长度的线程池Executors.newFixedThreadPool(10):
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

newFixedThreadPool不允许有临时线程,其堵塞队列为无限长的链表式队列。

2)调用临时线程池Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

newCachedThreadPool()不允许有正式线程,临时线程的数量不限制,SynchronousQueue()是一个容量为1的堵塞队列

5 ThreadPoolExecutor类是一个线程池类:

下面是它的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 
  • corePoolSize:正式线程的最大数量
  • maximumPoolSize:正式线程+临时线程的最大数量
  • keepAliveTime:这两个表示允许临时线程的最大空闲时间
  • TimeUnit unit:keepAliveTime的时间单位
  • BlockingQueue workQueue:传递任务的堵塞队列
  • ThreadFactory threadFactory:创建线程的工厂
  • RejectedExecutionHandler handler:线程太多时的解决方案(拒绝处理任务时的策略

6 拒绝任务的策略:

方法execute(Runnable)中提交的新任务将在执行程序关闭时被拒绝,并且当执行程序对最大线程和工作队列容量使用有限边界并且饱和时。提供了四个预定义的处理程序策略:
1) ThreadPoolExecutor.AbortPolicy(默认)::丢弃任务并抛出RejectedExecutionException异常。

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

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

3) ThreadPoolExecutor.CallerRunsPolicy:由调用execute本身的线程(主线程)运行任务。 提供了一个简单的反馈控制机制,降低了新任务提交的速度

5) 可以定义和使用其他类型的RejectedExecutionHandler类。

7 线程池处理任务的流程:

主线程负责提交任务——execute(Runnable对象),当正式线程超过最大正式线程数时,且堵塞队列已经满了时,就可以创建临时线程,临时线程的最大空闲时间为keepAliveTime,如果所有的线程都不能处理任务了,那么就会执行拒绝策略RejectedExecutionHandler。

具体流程:

  • 创建一个线程池,在还没有任务提交的时候,默认线程池里面是没有线程的。
  • 线程池里还没有线程或者线程池里存活的线程数小于核心线程数corePoolSize时,这时对于一个新提交的任务,线程池会创建一个线程去处理提交的任务。此时线程池里面的线程会一直存活着,阻塞在那里等待任务队列的任务到来。
  • 当线程池里面存活的线程数已经等于corePoolSize了,对于一个新提交的任务,会被放进任务队列workQueue排队等待执行。而之前创建的线程并不会被销毁,而是不断的去拿阻塞队列里面的任务,当任务队列为空时,线程会阻塞,直到有任务被放进任务队列,线程拿到任务后继续执行,执行完了过后会继续去拿任务。
  • 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列也满了,这里假设 maximumPoolSize>corePoolSize(如果等于的话,就直接拒绝了),这时如果再来新的任务,线程池就会继续 创建临时线程 来处理新的任务,直到线程数达到maximumPoolSize,就不会再创建了。线程执行完当前任务,会有一个判断当前临时线程是否需要销毁的逻辑,如果拿任务时阻塞(说明队列中没有任务),那超过keepAliveTime时间就直接返回null并且销毁当前临时线程,直到线程池里面的线程数等于corePoolSize之后才不会进行线程销毁。
  • 如果当前的线程数达到了maximumPoolSize,并且任务队列也满了,这种情况下还有新的任务过来,那就直接采用拒绝的处理器进行处理。默认的处理器逻辑是抛出一个RejectedExecutionException 异常
1)ThreadPoolExecutor类的使用:

ExecutorService 引用可以指向一个线程池对象

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

public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        // 创建了一个
        // 最多有 10 个正式员工
        // 最多有 20 个员工包括正式和临时
        // 临时工空闲时间最多有 60 SECONDS
        // 传递任务的队列是一个 ArrayBlockingQueue(容量是 30)
        // 执行默认的拒绝策略:处理不过来之后抛出异常
        ExecutorService p1 = new ThreadPoolExecutor(
                10,
                20,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(30)
        );
        // ExecutorService 引用可以指向一个线程池对象
        // 怎么提交任务
        p1.execute(new Runnable() {
            @Override
            public void run() {
            }
        });
        //或者
        //提交一个可运行的任务执行
        p1.submit(new Runnable() {
            @Override
            public void run() {
            }
        });
        // 怎么关闭线程池
        p1.shutdown();  // 推荐使用上面的
        p1.shutdownNow();
    }
}

注意:
submit 返回一个Future对象,可以通过这个对象来获得工作线程执行的结果。

2)自定义一个固定长度的线程池:
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class CustomThreadPool {
    static class Worker extends Thread {
        private BlockingQueue<Runnable> workQueue;

        Worker(BlockingQueue<Runnable> workQueue) {
            this.workQueue = workQueue;
        }

        @Override
        public void run() {
            while (!Thread.interrupted()) {
                Runnable task = null;
                try {
         // 从队列中取任务,当队列为空时,线程就会阻塞,否则就一直工作
                    task = workQueue.take();
                } catch (InterruptedException e) {
                    break; // 收到中断通知,退出
                }
                // 完成任务,run是Runnable中的方法
                task.run();
            }
        }
    }

    // 1. 传递任务用的阻塞队列
    BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();

    // 2. 记录所有的线程对象
    List<Worker> workerList = new ArrayList<>();
     //nThreads指定固定线程池的线程数量
    CustomThreadPool(int nThreads) {
        // 创建所有的工作线程
        for (int i = 0; i < nThreads; i++) {
            Worker worker = new Worker(workQueue);
            worker.start();
            workerList.add(worker);
        }
    }

    // 主线程要做的就是把接到的任务放到队列中即可
    // 工作线程会在合适的时候过来取任务的
    void execute(Runnable task) throws InterruptedException {
        workQueue.put(task);
    }

    void shutdown() {
        // 让所有的线程都停止工作
        // 可以使用 interrupt 建议一个线程停下来
        for (Worker worker : workerList) {
            worker.interrupt();
        }
    }
    //测试
public static void main(String[] args) throws InterruptedException {
        CustomThreadPool pool = new CustomThreadPool(3);
        int i=0;
        while(i<10){
            pool.execute(() -> {
                try {
                    System.out.println("暂停5s");
                    Thread.sleep(5*1000);
                    System.out.println("暂停结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            i++;
        }

    }
}

你可能感兴趣的:(多线程,java)