JavaSE——多线程2:线程池详解

一、线程池介绍

        线程池(Thread Pool)是一种基于多线程处理的服务器架构,它预先创建并维护一组线程,用于处理异步任务或并发请求。线程池的设计目的是减少创建和销毁线程的开销,提高系统的响应速度和吞吐量。

(一)线程池的主要核心原理

  1. 创建一个池子,池子中是空的。
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子。下次再次提交任务时,不需要创建新的线程,直接复用已有的线程即可。
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。

二、线程池代码实现

实现步骤:

  1. 创建线程池
  2. 提交任务
  3. 所有的任务全部执行完毕,关闭线程池 

Excutors:线程池的工具类通过调用方法返回不同类型的线程池对象。

public static ExecutorService  newCachedThreadPool();              创建一个没有上限的线程池
public static ExecutorService  newFixedThreadPool(int nThreads);        创建有上限的线程池

(一)创建只有一个线程的线程池(了解)

private static void demo1() {
        // 创建只有一个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // 不能并发   假设有10个任务,只有一个执行,其他9个都在等待
        for (int i = 0; i < 10; i++) {
            executorService.submit(()-> System.out.println(Thread.currentThread().getName() + "执行了"));
        }
        // 异步任务执行完之后关闭线程池
//        executorService.shutdown();
        // 立即关闭线程池
        executorService.shutdownNow();
    }

运行结果:

(二)指定线程池中线程的数量(了解)

private static void demo2() {
        // 指定线程池中线程的数量
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 20; i++) {
            executorService.submit(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
        }
    }

运行结果:无论提交多少个新任务,只会创建指定线程数

(三)创造一个可以伸缩的线程池对象

private static void demo3() {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        executorService.submit(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
    }
}

运行结果:

(四)创建一个可以延迟的线程池对象

private static void demo4() {
    // 创建一个可以延迟的线程池对象
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    long start = System.currentTimeMillis();
    scheduledExecutorService.schedule(() -> {
        long end = System.currentTimeMillis();
        System.out.println(end-start); // 5018
        System.out.println("hello Thread");
    }, 5, TimeUnit.SECONDS);
}

运行结果:

三、自定义线程池——ThreadPoolExecutor

(一)自定义线程池的七个参数

参数参数解释参数范围
int corePoolSize核心线程数量不能小于0
int maximumPoolSize最大线程数量不能小于等于0,最大数量>=核心线程数
long keepAliveTime在指定的时间内回收线程不能小于0
TimeUnit unit时间单位用TimeUnit指定
BlockingQueue<Runnable> workQueue任务队列

不能为null

最大线程数中排满了,多余的队列就会在任务队列中排队等待

实现类:

ArrayBlockingQueue          有界队列

LinkedBlockingQueue        无界队列

PriorityBlockingQueue       优先队列

ThreadFactory threadFactory创建线程工厂

不能为null

传入Executors.defaultThreadFactory()

RejectedExecutionHandler handler任务的拒绝策略

不能为null

当线程池和任务队列都满了的时候的拒绝策略,该策略在主线程执行

默认传入:

new ThreadPoolExecutor.AbortPolicy()

1.自定义线程池任务拒绝策略

设置核心线程数为3,最大线程数为6,队列长度为3:

        提交3个任务:此时线程池中就会创建3个线程来处理这3个任务;

        提交5个任务:此时线程池中会创建3个线程来处理3个任务,剩余的2个任务就会在任务队列中排队等待,等有了空余的线程,后面2个任务才会被执行;

        提交8个任务:此时线程池中会创建3个线程来处理3个任务,后面3个任务就会在队列中排队等待,线程池创建临时线程处理剩下2个任务;

        因此,创建临时线程的条件:核心线程都在运行,任务队列中已经排满了,才会创建临时线程处理后面的任务。

        任务在执行的时候,并不一定会按照提交的顺序来执行,先提交的任务不一定先执行。

        提交10个任务:此时提交的任务数量已经超过了最大线程数+队列长度。处理方案:线程池中会创建3个线程来处理3个任务,有3个任务在队列中等待,线程池中会创建3个临时线程处理3个任务,剩下1个任务就会触发任务的拒绝策略。

2.代码实现线程池  

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 5,
        TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
        Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
// 执行
threadPoolExecutor.execute(() -> System.out.println(Thread.currentThread().getName() + "执行了"));

(二)封装线程池

线程池在整个项目中只有一个,所以可以将线程池与单例模式相结合。

public class ThreadPoolUtil {
    private ThreadPoolUtil() {
    }

    private static final ThreadPoolUtil THREAD_POOL_UTIL = new ThreadPoolUtil();

    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR;

    private static final ScheduledThreadPoolExecutor SCHEDULED_THREAD_POOL_EXECUTOR;

    static {
        THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(10, 100, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        SCHEDULED_THREAD_POOL_EXECUTOR = new ScheduledThreadPoolExecutor(5, Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }

    public static ThreadPoolUtil getInstance() {
        return THREAD_POOL_UTIL;
    }

    // 异步执行
    public void execute(Runnable runnable) {
        THREAD_POOL_EXECUTOR.execute(runnable);
    }

    // 延迟执行
    public void delay(Runnable runnable, long time, TimeUnit timeUnit) {
        SCHEDULED_THREAD_POOL_EXECUTOR.schedule(runnable, time, timeUnit);
    }

    // 传入Callable接口的实现类
    public <V> void task(Callable<V> callable) {
        THREAD_POOL_EXECUTOR.submit(callable);
    }

    // 停止线程
    public void release() {
        THREAD_POOL_EXECUTOR.shutdown();
    }

    public void releaseNow() {
        THREAD_POOL_EXECUTOR.shutdownNow();
    }

    public void shutdown() {
        SCHEDULED_THREAD_POOL_EXECUTOR.shutdown();
    }

    public void shutdownNow() {
        SCHEDULED_THREAD_POOL_EXECUTOR.shutdownNow();
    }
}

测试封装后的线程池:

public static void main(String[] args) {
    // 测试封装后的线程池
    ThreadPoolUtil3.getInstance().execute(() -> System.out.println(Thread.currentThread().getName() + " hello Thread"));
}

// pool-1-thread-1 hello Thread

(三)最大并行数

使用代码可以获取当前电脑的最大并行数:

public class MyThreadPoolDemo2 {
    public static void main(String[] args) {
        int count = Runtime.getRuntime().availableProcessors();
        System.out.println(count); // 6
    }
}

线程池的大小:

计算比较多,读取本地文件和连接数据库的操作比较少,使用:

        CPU密集型运算:最大并行数+1

读取本地文件和连接数据库的操作比较多,使用:

        I/O密集型运算:最大并行数 * 期望CPU利用率 * (总时间(CPU计算时间+等待时间) / CPU计算时间)

四、ThreadLocal

(一)ThreadLocal基本介绍

        每一个线程有一个自己的ThreadLocal,ThreadLocal本质上是线程的一个映射。

        ThreadLocal 是 Java 中的一个类,它提供了线程局部变量。每个使用该变量的线程都有其自己的独立初始化变量副本,因此每个线程可以访问到自己的线程局部变量,而不会影响到其他线程中的变量。这在多线程编程中非常有用,因为它可以避免共享资源的同步问题。

        ThreadLocal的作用:在线程之间传递数据,即从线程的上游到线程的下游;解决并发安全的问题。

出现线程安全隐患的条件:

  • 共享资源
  • 写操作
  • 多线程(通过加锁,可以破坏多线程,局部变成单线程)

(二)使用ThreadLocal传递数据 

public class TestDemo3 {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        String str = "hello e";
        threadLocal.set(str);
        a();
    }

    private static void a() {
        b();
    }

    private static void b() {
        c();
    }

    private static void c() {
        d();
    }

    private static void d() {
        e();
    }

    private static void e() {
        System.out.println(threadLocal.get());
    }
}

(三)解决线程安全隐患——破坏共享资源 

        定义一个打印机类,再定义两个线程,调用打印机,实现论文打印的功能,每个线程打印3行论文,并且一个线程打印完,另一个线程才能打印。

public class TestDemo4 {
    public static void main(String[] args) {
        Printer printer = new Printer();
        new Thread(new GFS(printer)).start();
        new Thread(new BFM(printer)).start();
    }
}

class Printer {
    public void print(String str) {
        System.out.println("打印机在打印" + str);
    }
}

class GFS implements Runnable {
    private Printer printer;

    public GFS(Printer printer) {
        this.printer = printer;
    }

    @Override
    public void run() {
        printer.print(printer + "高富帅");
        printer.print(printer + "高富帅");
        printer.print(printer + "高富帅");
    }
}

class BFM implements Runnable {
    private Printer printer;

    public BFM(Printer printer) {
        this.printer = printer;
    }

    @Override
    public void run() {
        printer.print(printer + "白富美");
        printer.print(printer + "白富美");
        printer.print(printer + "白富美");
    }
}

运行结果:打印输出是乱序的


使用synchronized锁,使其连续打印:

class GFS implements Runnable {
    private Printer printer;

    public GFS(Printer printer) {
        this.printer = printer;
    }

    @Override
    public void run() {
        synchronized (printer) {
            printer.print(printer + "高富帅");
            printer.print(printer + "高富帅");
            printer.print(printer + "高富帅");
        }
    }
}

class BFM implements Runnable {
    private Printer printer;

    public BFM(Printer printer) {
        this.printer = printer;
    }

    @Override
    public void run() {
        synchronized (printer) {
            printer.print(printer + "白富美");
            printer.print(printer + "白富美");
            printer.print(printer + "白富美");
        }
    }
}

运行结果:

 


加锁是破坏多线程,使其每次只执行一个线程,除了这种方法,我们也可以共享资源:

通过重写ThreadLocal的initialValue方法,使其返回值为指定的值,源码默认为null:

protected T initialValue() {
    return null;
}
public class TestDemo4 {
    public static ThreadLocal<Printer> threadLocal = new ThreadLocal<>() {
        // 匿名内部类,重写initialValue方法
        @Override
        protected Printer initialValue() {
            return new Printer();
        }
    };

    // lambda表达式
//    static ThreadLocal<Printer> threadLocal = ThreadLocal.withInitial(() -> new Printer());

    public static void main(String[] args) {
        new Thread(new GFS()).start();
        new Thread(new BFM()).start();
    }
}

class Printer {
    public void print(String str) {
        System.out.println("打印机在打印" + str);
    }
}

class GFS implements Runnable {
    @Override
    public void run() {
        Printer printer = TestDemo4.threadLocal.get();
        printer.print(printer + "高富帅");
        printer.print(printer + "高富帅");
        printer.print(printer + "高富帅");
    }
}

class BFM implements Runnable {

    @Override
    public void run() {
        Printer printer = TestDemo4.threadLocal.get();
        printer.print(printer + "白富美");
        printer.print(printer + "白富美");
        printer.print(printer + "白富美");
    }
}

运行结果:打印虽然是乱序的,但是资源是一个线程一份的,地址值有2个:

 

ThreadLocal与synchronized锁:

当需要共享资源的时候,例如卖票,就使用synchronzied锁;

当一个线程一份资源的时候,就使用ThreadLocal。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值