线程池的使用

线程池的作用

  1. 降低线程创建和销毁的开销:线程的创建和销毁是比较昂贵的操作。通过使用线程池,可以避免频繁地创建和销毁线程,而是复用线程池中已经存在的线程,从而降低了开销。

  2. 控制并发度:通过控制线程池中线程的数量,可以控制程序的并发度。这样可以避免过多的线程竞争和资源的浪费,提高程序的稳定性和性能。

  3. 提供线程的管理和监控:线程池可以方便地管理和监控线程的状态和执行情况。可以通过线程池的接口来获取线程的执行结果、取消任务、设置优先级等。

  4. 提供任务队列:线程池通常会有一个任务队列,用于保存待处理的任务。当线程池中的线程空闲时,可以从任务队列中取出任务进行处理,避免任务的丢失和过载。

  5. 控制资源消耗:由于线程池可以限制线程的数量,可以有效地控制系统资源的消耗,避免因过多的线程而导致系统崩溃或资源耗尽的问题。

了解了线程池的作用后,来了解一下线程吃的创建吧!

在java标准库中,提供了线程的线程池,可以直接使用.

工厂模式

1.上面这种操作,使用了Executors类的静态方法直接构造出一个对象,(相当于把new 操作隐藏 到方法里面了 ),像这样的方法叫做工厂方法,提供了工厂方法的类叫做工厂类.

  1. 在构造方法中,如果方法名相同,参数的个数或者类型不同,就会发生重载,而重载是有一定局限性的,有的构造方法可能就会出现两个方法参数的个数和类型是相同的情况,在这种情下重载是无法实现的,为了解决这种问题,就可以使用工厂模式,所谓工厂模式,就是使用普通的方法来代替构造方法来创建对象

在上面我们已经创建了一个带有10个线程的线程池,在线程中有一个submit方法,可以给线程池提交若干个任务

运行结果

  1. 通过运行结果发现,main线程已经结束,而整个进程还没有结束,是因为线程池中的线程都是 "前台线程",此时会阻止进程的结束.

  2. 当使用submit方法给线程池分配任务的时候,线程池中的线程都是随机执行的.


变量捕获

上面的代码我们换种写法编译器会提示我们有错误

为什么不能直接使用 i 呢?

上面的run方法属于Runnable(),但这个方法的执行时机不是立刻执行,而是在未来的某个节点上执行,但随着主线程的执行,可能for循环走完了但run方法在线程池中还没有开始执行,此时i就要被销毁了,为了避免作用域的差异,导致后面执行run时i已经被销毁,就有了变量捕获,也就是让run方法把刚才的i在栈上拷贝一份...后面在执行到的时候直接赋值就可以.


线程池的创建方法

上面的的这些线程池,本质上都是包装ThreadPoolExecutor来实现的.

在java标准库中,给ThreadPoolExecutor提供了四个构造方法,在这四个构造方法都很类似,但最后一个更为复杂,接下来看看第四个构造方法里每个参数代表的含义.

而我们在实际开发中线程池中的线程数设置为多少合适呢?此时要考虑两个极端情况

  1. cpu密集型, 每个线程要执行的任务都是狂转cpu(进行一系列算数运算),此时线程池的线程数最也不要超过cpu的核数,因为cpu密集型任务,要一直占用cpu,线程多,但cpu已经不够分了

  2. IO密集型.每个IO干的工作就是等待IO,比如读写硬盘,读写网卡,等待用户输入等操作,此时这些线程处于阻塞状态,不参与cpu调度,不在受制于CPU核数了.

然而在实际开发中,没有完全符合这两中理想模型的,具体程序中,有的程序吃cpu,有的程序是等待IO的,要设置多少线程数量是不确定的,要是消耗cpu多,等待IO少,线程数目就可以少设置一点,要是消耗cpu少,等待IO多,则可以多设置一点线程数量.在平常中我们可以进行测试来看看不同线程数量占用系统资源的情况.

线程池中使用的是阻塞队列,每个线程都是在不停尝试take的,如果有任务就take成功,否则就进行阻塞等待.

所谓拒绝策略,也是一个特殊的对象,描述了当线程池的任务队列满了,如果继续天剑任务会有什么样的行为.Java标准库提供了四种拒绝策略.

接下来模拟实现一个简单的固定线程数量的线程池

一个线程池至少有两部分,一个是阻塞队列用来保存任务,另外就是有若干个线程来执行任务.

复制代码
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class MyThreadPool {
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    //创建线程
    //n表示线程数量
    public MyThreadPool(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                while(true) {
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }

    //注册任务给线程池
    public void submit(Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
相关推荐
Grey Zeng7 小时前
Java SE 25新增特性
java·jdk·jdk新特性·jdk25
雨白8 小时前
Java 线程通信基础:interrupt、wait 和 notifyAll 详解
android·java
架构师沉默13 小时前
设计多租户 SaaS 系统,如何做到数据隔离 & 资源配额?
java·后端·架构
Java中文社群14 小时前
重要:Java25正式发布(长期支持版)!
java·后端·面试
每天进步一点_JL15 小时前
JVM 类加载:双亲委派机制
java·后端
用户2986985301416 小时前
Java HTML 转 Word 完整指南
java·后端
渣哥16 小时前
原来公平锁和非公平锁差别这么大
java
渣哥16 小时前
99% 的人没搞懂:Semaphore 到底是干啥的?
java
J2K16 小时前
JDK都25了,你还没用过ZGC?那真得补补课了
java·jvm·后端
kfyty72516 小时前
不依赖第三方,不销毁重建,loveqq 框架如何原生实现动态线程池?
java·架构