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

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

一、💛

线程池的基本介绍

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

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

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

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

不用线程池:如果是系统这里创建线程,需要调用系统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月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
497 2
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
701 60
【Java并发】【线程池】带你从0-1入门线程池
|
Java
线程池是什么?线程池在实际工作中的应用
总的来说,线程池是一种有效的多线程处理方式,它可以提高系统的性能和稳定性。在实际工作中,我们需要根据任务的特性和系统的硬件能力来合理设置线程池的大小,以达到最佳的效果。
326 18
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
813 14
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
419 4
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
2545 2
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。

热门文章

最新文章