【JavaEE】多线程之线程池

简介: 【JavaEE】多线程之线程池

1.线程池基础知识

1.1线程池概念

在多线程当中,并发程度会不断提升,随之带来的是线程频繁的创建和销毁,此时程序的开销就会变大,为了改善这个现象,就引入了线程池,程序将创建好的线程放入到“线程池”中,当程序需要用时,可以直接在这个“池子”中取出来,线程用完之后再放入到“池子”中。这样在就不会再频繁的创建和销毁了,可以使程序开销减小,效率更高。

1.2线程池使程序更高效的原理

在创建、销毁线程时我们时是交给操作系统内核来完成的, 而我们使用线程池调度是以用户态实现的。

如果将任务交给操作系统的内核去完成,它的完成时间是不可控的,这是为什么呢?原因在于:内核需要对多个任务提供服务,在你交给它任务时,它可能无法仅仅单一的完成你交与的任务,可能还需要完成别人交与它的任务。

而用户态就不一样了,用户态就仅会对自己的任务负责,因此效率更高,开销更小。


🐣下面这个例子可以让你更加容易理解(银行办事):

有一天你去银行办理业务,排队排了好久,工作人员问你:拿身份证复印件了吗?你心想:卧槽还要这个。你回答:没有拿。然后工作人员说,没关系,你有两个选择:

1.我给你打印然后办理。(相当于内核态)

2.你自己打印然后给我。 (相当于用户态)


选择1:当让工作人员打印,可能他手头上出了你的打印东西在这个事还有其他的事,因此让他帮打印的话,打印好就不知道事猴年马月了。

选择2: 我自己直接去大厅的复印机这里,直接就打印好了,然后拿给工作人员,办完就跑。

很明显,在1、2选择中,选择2更高效,这也就是为什么线程池更高效了。  



2.标准库中的线程池

2.1线程池的继承关系



2.2线程池的构造方法

Java中提供了线程池的标准类(ThreadPoolExecutor),构造方法(常用的一个)如下:

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


对构造方法中参数解读

1. corePoolSize(核心线程);相对于公司的正式员工,签了劳动合同的,不能够随意开除。

2.maximumPoolSize (最大线程数):线程最多不能超过这这个数,最大线程数=核心线程+非核心线程;非核心线程就相当于公司招来的实现生。

3.keepAliveTime(非核心线程的存活时间):如果找来的实习生一直摸鱼没有活干,超过这个时间就将他炒鱿鱼。

4.unit:上方存活时间的单位

5.workQueue(用于缓存未执行的任务):线程的任务队列,通过submit方法加入的任务进入这个队列

6.threadFactory(线程工厂):线程的创建方案。

7.handler(拒绝策略):当任务对象线程满了的话,应该如何做出回答。


在Java总handler有四种拒绝策略:


1688790246375.png


2.3线程池的使用

线程池在实现时,我们需要用到工具类Executors,调用里面的静态方法来完成创建的,他的所有方法的返回值都是ExecutorService。

import java.util.concurrent.*;
public class testDemo {
    public static void main(String[] args) {
        //创建一个固定数量的线程池
        // 1. 创建一个操作无界队列且固定大小线程池
        ExecutorService pool1 = Executors.newFixedThreadPool(10);
        //线程池中线程的数量是动态变化的
        // 2. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存
        ExecutorService pool2 = Executors.newCachedThreadPool();
        //线程池中只有一个线程
        // 3. 创建一个操作无界队列且只有一个工作线程的线程池
        ExecutorService pool3 = Executors.newSingleThreadExecutor();
        //线程池中只有一个线程+定时器功能
        // 4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。
        ExecutorService pool4 = Executors.newSingleThreadScheduledExecutor(Executors.defaultThreadFactory());
        //创建一个固定数量的线程池+定时器功能
        // 5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。
        ExecutorService pool5 = Executors.newScheduledThreadPool(3, Executors.defaultThreadFactory());
        // 6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务,不保证处理顺序
        ExecutorService pool6 = Executors.newWorkStealingPool();
        // 7. 自定义线程池
        ExecutorService pool7 = new ThreadPoolExecutor(3,
                10,
                10000,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
    }
}


3.线程池的优点

  1. 1.降低资源消耗:减少线程的创建和销毁带来的性能开销。
  2. 2.提高响应速度:当任务来时可以直接使用,不用等待线程创建
  3. 3.可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。


4.线程池的模拟实现

核心操作为 submit, 将任务加入线程池中

使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.

使用一个 BlockingQueue 组织所有的任务

每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.

指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了.

import java.util.concurrent.LinkedBlockingQueue;
class Worker extends Thread {
    private LinkedBlockingQueue<Runnable> queue = null;
    public Worker(LinkedBlockingQueue<Runnable> queue) {
        super("worker");
        this.queue = queue;
    }
    @Override
    public void run() {
        // try 必须放在 while 外头, 或者 while 里头应该影响不大
        try {
            while (!Thread.interrupted()) {
                Runnable runnable = queue.take();
                runnable.run();
            }
        } catch (InterruptedException e) {
        }
    }
}
public class MyThreadPool {
    private int maxWorkerCount = 10;
    private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue();
    public void submit(Runnable command) throws InterruptedException {
        if (queue.size() < maxWorkerCount) {
            // 当前 worker 数不足, 就继续创建 worker
            Worker worker = new Worker(queue);
            worker.start();
        }
        // 将任务添加到任务队列中
        queue.put(command);
    }
}


严重线程池是否模拟实现完成 代码运行如下:

public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool();
        for (int i = 0; i < 10; i++) {
            int j=i;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello"+j);
                }
            });
            Thread.sleep(1000);
        }


运行结果如下:



相关文章
|
4天前
|
Python
|
6天前
|
Java 数据库
【Java多线程】对线程池的理解并模拟实现线程池
【Java多线程】对线程池的理解并模拟实现线程池
17 1
|
3天前
|
NoSQL Redis 缓存
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?
【5月更文挑战第17天】Redis常被称为单线程,但实际上其在处理命令时采用单线程,但在6.0后IO变为多线程。持久化和数据同步等任务由额外线程处理,因此严格来说Redis是多线程的。面试时需理解Redis的IO模型,如epoll和Reactor模式,以及其内存操作带来的高性能。Redis使用epoll进行高效文件描述符管理,实现高性能的网络IO。在讨论Redis与Memcached的线程模型差异时,应强调Redis的单线程模型如何通过内存操作和高效IO实现高性能。
28 7
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?
|
5天前
|
监控 Java 测试技术
在多线程开发中,线程死循环可能导致系统资源耗尽,影响应用性能和稳定性
【5月更文挑战第16天】在多线程开发中,线程死循环可能导致系统资源耗尽,影响应用性能和稳定性。为解决这一问题,建议通过日志记录、线程监控工具和堆栈跟踪来定位死循环;处理时,及时终止线程、清理资源并添加错误处理机制;编码阶段要避免无限循环,正确使用同步互斥,进行代码审查和测试,以降低风险。
18 3
|
6天前
|
设计模式 消息中间件 安全
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
13 0
|
6天前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
26 1
|
6天前
|
存储 缓存 安全
【Java多线程】线程安全问题与解决方案
【Java多线程】线程安全问题与解决方案
22 1
|
6天前
|
Java 调度
【Java多线程】线程中几个常见的属性以及状态
【Java多线程】线程中几个常见的属性以及状态
14 0
|
6天前
|
Java 调度
【Java多线程】对进程与线程的理解
【Java多线程】对进程与线程的理解
15 1
|
6天前
|
存储 安全 Java
【探索Linux】P.21(多线程 | 线程同步 | 条件变量 | 线程安全)
【探索Linux】P.21(多线程 | 线程同步 | 条件变量 | 线程安全)
15 0