线程池,以及线程池的实现以及面试常问的问题,工厂模式,常见的锁策略(面试常考,要了解,不行就背)

简介: 线程池,以及线程池的实现以及面试常问的问题,工厂模式,常见的锁策略(面试常考,要了解,不行就背)

一、💛

线程池的基本介绍

内存池,进程池,连接池,常量池,这些池子概念上都是一样的~~

如果我们需要频繁的创建销毁线程,此时创建销毁的成本就不能忽视了,因此就可以使用线程池。

提前创建好一波线程,后续需要使用线程,就直接从池子里面拿一个即可,当线程不再使用,就放回池子里面。(本来是需要创建线程/销毁线程,现在是从池子里面获取到现成的线程,并且把线程归还到池子里面

那么为啥从池子里面拿就比系统里面创建线程更加高效呢?

不用线程池:如果是系统这里创建线程,需要调用系统API,进一步的由操作系统内核完成,完成线程的创建过程(内核是给所有进程提供服务的,这样你想干的事情,就需要等一等,等多长时间我们是未知的,是不可以控制的)

使用线程池:上述的内核中进行的操作都是提前做好了的,现在的取线程过程,纯粹的是用户使用代码完成的(纯用户态)-是可控制的。

二、💙

工厂模式:去生产的功能(字面意思),用于生产对象,一般情况下我们创建对象都是new,通过构造方法,但是构造方法有时候存在巨大的缺陷(构造方法是固定就是类名,有的类需要使用多种不同构造方式->(方法重载仅要求参数的个数和类型有区别)

~比如说:表示坐标->这种无法构成重载

public class Circle {
    public Circle(double x,double y){         //笛卡尔坐标
    };
    public  Circle(double r,double a){        //极坐标
    };
}
所以上面的代码也不对,构不成方法重载

使用工厂模式来解决上述问题,不使用构造方法来,(我刚开始也在想为什么是静态,直到我自己去试一下,明白了也就是说不用构造方法创建对象,假如不是静态方法,那么该怎么调用他呢,不是静态的,可是只能用对象.方法才可以调用)用普通方法来构造对象,这样方法就可以任意的了,普通方法内部去new对象~由于普通方法的目的是创建对象,然后调用方法来设置属性,所以方法一般都是静态的。(类名.方法)

三、💜

//Executors:工厂类,后面的是工厂方法。这句话是创建一个固定线程数量的线程池
//线程池对象:ExecutorService service
        ExecutorService service= Executors.newFixedThreadPool(4);
//创建一个线程数组,动态变化的线程池。
        ExecutorService service2= Executors.newCachedThreadPool();
//包含单个线程(比原生创建API更简单一些指Thread)
        ExecutorService service3= Executors.newSingleThreadExecutor();
//类似于定时器效果,添加一些任务,执行,被执行的时候,不是只有有一个扫描线程来执行,可能是有多个共同执行
        ExecutorService service4= Executors.newScheduledThreadPool();
    }

 

四、❤️

面试题:谈谈java库中的线程池构成方法的参数和含义

这个方法最复杂,而且别的参数这个参数都有,所以就只解释这个方法就行。

int corePoolSize:核心线程数

int maximunPoolSize:最大线程数

ThreadPoolExcutor:里面的线程个数,并

非固定不变的,会根据当前任务的情况动态变化(自适应)

corePoolSize:至少要这些线程,哪怕你的线程都没任务也要这些个线程(如同公司里面的正式员工)

maximumPoolSize:最多不超过这些线程,哪怕干冒烟了,也不能比这个更多了(正式员工+实习生)

long keepAliveTime, TimeUnit unit:实习生线程,空闲时间超过指定阈值(允许实习生摸鱼的最大时间),就可以销毁了

BlockingQueue<Runnable>workQueue:线程池内部有很多很多,任务可以使用阻塞队列管理,线程池可以内置阻塞队列,也可以手动一个。

RejectedExecutionHandler   handler:线程池考的重点,拒绝方式/拒绝策略,线程池有一个阻塞队列,当队列满了,继续加任务如何处理。

1.ThreadPoolExecutors.AbortPolicy:直接抛出异常,线程池就不干活了,(小王喊我打球,我在学习,我直接崩溃了,我哇哇大哭)

2.ThreadPoolExecutor.callerRunsPolicy:谁说添加这个任务的线程,谁就去执行这个任务

,我会直接说:我没空,自己投去吧)

3.ThreadPoolExecutor.DiscardPolicy:把新的任务丢弃,(不打球了,我接着学习)

4.ThreadPoolExecutor.DiscardPolicy:丢弃最早的任务,执行新的任务(放弃学习,去打球)

有的线程公司会推荐使用这个。

 

五、💚

具体实现一个线程池

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool {
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
//通过这个方法把任务添加到线程池中
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
//n表示一个线程池里面有几个字段,创建了一个固定数量的线程池
    public MyThreadPool(int n) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                while (true) {
                    Runnable runnable = null;
                    try {
                        runnable = queue.take();      //从队列中提取这个任务
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    runnable.run();                  //运行这个任务
                }
            });
            t.start();                              //开启这个线程
        }
    }
}
public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        //获取当前引用实例
        MyThreadPool myThreadPool = new MyThreadPool(4);
        for (int i = 0; i < 1000; i++) {               //循环很关键哈,这个循环是添加1000次任务,但是假如你放到run里面,就会变成一个任务,至少内容是执行1000遍,线程是执行多个任务,但是不能一个任务还分担
            myThreadPool.submit(new Runnable() {           //(安排任务,安排一个任务)
                @Override                                  //这个任务进行的工作
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " love");
                }
            });
        }
    }
}

面试问题2号:创建线程池的时候,线程个数怎么数的:

网路上查资料很多:假设cpu逻辑数是N,线程的个数:N,N+1,2N···

准确的说都不准确,因为不同的项目要做的工作是不同的:

cpu密集型线程工作:全是运算大部分工作在cpu上完成,cpu给他安排核心,才能概括,假如cpu N个核心,线程数量最好也是为N,如果多了,线程也只能是排队等待,没有新的进展

Io密集型线程工作:涉及大量等待时间,等待的过程,不要cpu,所以这里线程多,也会给cpu造成负担,cpu16核,整个32个线程,不犯毛病(不耗cpu,甚至cpu占用很低)———

实际上,一部分cpu密集,一部分Io密集,是我们工作中的常态,此时一个线程多少在cpu上执行,多少等待IO,说不好,更好的做法:自己去性能测试一下,找到性能和开销比较均衡的数值


六、💔

多线程进阶开启:

常见的锁策略

如果工作中,真正要实现一把锁,需要理解锁策略

1.乐观锁VS悲观锁

乐观锁:预测,不太会出现锁冲突的情况

悲观锁:预测,这个场景非常容易锁冲突

2.重量级锁VS轻量级锁

重量级锁:加锁开销比较大,花的时间多,占有系统资源,一个悲观锁,很可能是重量级锁

轻量级锁:花的时间少,占有资源少,加锁的开销比较小的,很可能是乐观锁

悲观,乐观是加锁之前堆冲突概率的预测,决定工作的多少,重量,轻量,是加锁后,考虑实际的锁开销

3.自旋锁VS刮起等待锁

自旋锁是轻量级的一种典型实现,在用户态通过自旋的方式(while循环),实现类似加锁的操作(一直在疯狂的舔,这种锁会耗一定的cpu,但是是最快速度拿到锁的)

挂起等待锁:通过内核态,借助系统提供的锁机制,当出现锁冲突,会牵扯到内核对线程的调度,是冲突的线程出现挂起(阻塞等待)重量级锁的一种典型体现,发现锁被占用后,自己该干啥干啥,偶尔听到了消息,又去找这个锁,耗费cpu少,但无法第一时间拿到锁(小摆烂)

4.读写锁VS互斥锁

读写锁:把读操作,写操作分开了

假如两个线程 一个读加锁,另一个还是读加锁,那么两个不会有锁竞争(目的:就是把这种情况处理,这样多线程的效率会更高)

一个读,一个写,两个都是写都会有锁竞争,但是两个读没事,在开发中读操作会比写操作更加频繁

互斥锁:写了就不能读,读了就不可以写,

5.公平锁VS非公平锁

公平锁是遵循先来后到这个规则的

非公平锁相当于超市促销,都来抢位置,不遵守顺序

操作系统自带锁(pthread-mutex)是非公平锁,要实现公平锁,就需要一些额外的数据结构来支持(比如需要有办法记录每个线程的阻塞等待时间)


相关文章
|
7月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
348 60
【Java并发】【线程池】带你从0-1入门线程池
|
5月前
|
Java
线程池是什么?线程池在实际工作中的应用
总的来说,线程池是一种有效的多线程处理方式,它可以提高系统的性能和稳定性。在实际工作中,我们需要根据任务的特性和系统的硬件能力来合理设置线程池的大小,以达到最佳的效果。
137 18
|
8月前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
8月前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
556 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
8月前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
266 16
|
7月前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
9月前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
4月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
186 0
|
7月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
130 26
|
7月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
133 17

热门文章

最新文章