这里写目录标题

  • JUC介绍
    • 基本概念
    • JUC中的类和接口
    • 主要包含功能
  • 线程池
    • 为什么使用线程池
    • 什么是线程池
    • 使用线程池的特点
      • 优点
      • 缺点
  • JUC中的线程池
    • Executor介绍
    • ThreadPoolExecutor
      • ThreadPoolExecutor是JUC中提供的默认线程池实现类
      • 构造方法
      • 参数详解
      • corePoolSize 核心线程数
      • 2. workQueue 阻塞队列
      • 3. maximumPoolSize 最大线程数
      • 4. keepAliveTime 线程最大空闲时间
      • 5. unit keepAliveTime 时间单位
      • 6. threadFactory 线程工厂
      • 7. handler 线程池拒绝策略(面试题:线程池拒绝策略有哪些)
  • 8. 线程池总结 (常见面试题)
  • Executors
    • newCachedThreadPool()
    • newFixedThreadPool(int)
    • newScheduledThreadPool(int)
    • newWorkStealingPool()
    • ForkJoinPool介绍
    • 线程池代码演示
  • JUC中的AtomicInteger原子类
    • 原子性介绍
    • JUC中原子类
    • 使用AtomicInteger保证线程安全
      • 原始方式: 使用synchronized同步锁解决
      • 原子类方式: 使用AtomicInteger原子类
      • 原子类AtomicInteger方法介绍
      • 原子类AtomicInteger底层实现
    • Volatitle(面试题)
      • 可见性
      • 可见性原理
      • 嗅探机制工作原理
      • 有序性
    • CAS算法(面试题)
      • CAS算法介绍
      • 优点
      • 缺点
      • ABA问题
      • 解决ABA问题
    • 源码分析AtomicInteger原子类
      • 底层实现
      • 源码重点

JUC介绍

基本概念

从Java 5开始,在JDK中多出了java.util.concurrent包(简称:JUC)。
JUC主要是让开发者在多线程编程中更加简单、方便一些。
通过JDK内置了一些类、接口、关键字,补充完善了JDK对于并发编程支持的“短板

JUC中的类和接口

通过IDEA可以看java.util.concurrent包中所有的类和接口

主要包含功能

Executor:线程池
Atomic:原子操作类
Lock:锁
Tools:信号量工具类
并发集合:提供了线程安全的集合类。

线程池

为什么使用线程池

线程的创建和线程的销毁都会消耗一定的系统资源。
如果需要频繁的新建线程、销毁线程,对于系统可能需要消耗大量资源。
如何在需要频繁新建、销毁线程的场景下保证系统响应时间更快,消耗资源更少?

只有使用线程池

什么是线程池

内存中的一块空间。这块空间里面存放一些已经实例化好的线程对象。
当代码中需要使用线程时直接从线程池获取。当代码中线程执行结束或需要销毁时,把线程重新放入回到线程池,而不是让线程处于死亡状态

使用线程池的特点

优点

1.降低系统资源消耗。通过重用线程对象,降低因为新建和销毁线程产生的系统消耗。
2.提高系统响应速度。直接从内存中获取线程对象,比新建线程更快。
提供线程可管理性。通过对线程池中线程数量的限制,避免无限创建线程导致的内存溢出或CPU资源耗尽等问题

缺点

默认情况下,无论是否需要使用线程对象,线程池中都有一些线程对象,也就是说会占用一定内存

JUC中的线程池

Executor介绍

线程池的顶级接口,所有线程的子类


ThreadPoolExecutor

ThreadPoolExecutor是JUC中提供的默认线程池实现类

构造方法

提供了四种参数列表的构造方法

其中包含7个参数的构造方法, 这是ThreadPoolExecutor支持的所有参数

参数详解

corePoolSize 核心线程数

创建线程池后,默认线程池中并没有任何的线程,执行了从线程池获取线程的时候才会创建核心线程并返回. 如果没有达到指定的corePoolSize, 即使有空闲的核心线程, 也会创建新的核心线程执并返回, 直到达到了corePoolSize. 达到corePoolSize后, 从线程池获取线程, 有空闲的核心线程, 就返回空闲的线程
理解: 线程池就相当于一个公司, 核心线程数就相当于正式工人数, 新公司成立, 接一个新任务就会招聘一个正式工, 即使有空闲的正式工, 来了新任务也会招聘新的正式工来完成新任务, 直到招满为止. 招满后, 新任务就由空闲的正式工来完成

2. workQueue 阻塞队列

当线程池中的线程数目达到指定的corePoolSize后,并且所有的核心线程都在使用中, 再来获取线程将会被添加到缓存任务的阻塞队列中,也就是workQueue
队列可以设置queueCapacity 参数,表示任务队列最多能存储多少个线程对象
理解: 正式工已经招满, 此时所有的正式工都在忙着工作, 再来的新任务, 就只能排队等待

3. maximumPoolSize 最大线程数

被使用的线程数大于或等于核心线程数,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。
被使用的线程数等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常
理解: 正式工已经招满, 此时所有的正式工都在忙着工作, 且队列中的新任务已达到队列上线, 需要招聘临时工, 来完成任务. 正式工和临时工的总数为公司的最大员工数, 如果所有的临时工也都有任务, 再来新任务公司予以拒绝

4. keepAliveTime 线程最大空闲时间

线程池中没有被使用的线程, 就会处于空闲(alive)状态, 只要超过keepAliveTime, 空闲的线程就会被销毁,直到线程池中的线程数等于corePoolSize
如果设置了allowCoreThreadTimeOut=true(默认false),核心线程也可以被销毁
理解: 到了淡季, 公司不忙了, 很多的正式工和临时工都空闲了, 临时工就直接解雇了, 而正式工没有重大违纪的不会解雇

5. unit keepAliveTime 时间单位

TimeUnit是枚举类型
包含如下时间单位

注:
1秒 = 1000毫秒 1毫秒 = 1000微秒 1微妙 = 1000纳秒

6. threadFactory 线程工厂

主要用来创建线程

7. handler 线程池拒绝策略(面试题:线程池拒绝策略有哪些)

只有核心线程都在使用中,任务队列已满,且线程数量已经达到maximunPoolSize才会触发拒绝策略。或在调用shutdown()和真正关闭线程池之间提交的任务都会被决绝。因为线程池被shutdown()时,会等待线程池内线程对象执行结束,才关闭线程池


DiscardoldestPolicy: 从队列中去掉一个最先进入队列的任务。然后重新提交被拒绝的任务(重复此过程)
AbortPolicy:丢弃任务,抛出运行时异常(RejectedExecutionException)(默认值)
CallerRunsPolicy:由调用该任务的线程处理, 线程池不参与
DiscardPolicy:丢弃任务,不抛出异常

8. 线程池总结 (常见面试题)

corePoolSize: 核心线程数大小
maximunPoolSize:最大线程数大小(包含核心线程数,剩下的就是普通线程数)
queueCapacity:任务队列最大长度
keepAliveSize:线程最大空闲时间
allowCoreThreadTimeOut:核心线程超时是否被销毁(默认 false)
handler:拒绝策略
workQueue:任务队列类型

1.当线程数小于核心线程数时,创建线程, 直到到达核心指定的核心线程数
2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列
3.当线程数大于等于核心线程数,且任务队列已满
若线程数小于最大线程数,创建线程, 直到达到最大线程数
若线程数等于最大线程数,抛出异常,拒绝任务

Executors

Executors可以帮助快速实例化特定类型的线程池对象, Executors属于一个工具类
返回值都是ExecutorService接口的实现类, 底层都是调用ThreadPoolExecutor()

newCachedThreadPool()

不需要要参数,基本上都是默认值

构造方法参数:
corePoolSize: 0
maximumPoolSize: Integer的最大值
keepAliveTime: 60秒
workQueue:SynchronousQueue
同步队列, 一个线程向队列put(放入)数据后,阻塞等待被take(取出)
效果总结:线程需要时就新建,空闲60秒后被销毁。默认都是放在同步队列中。

newFixedThreadPool(int)

构造方法参数:
corePoolSize: 参数值
maximunPoolSize: 参数值
keepAliveTime: 0秒
workQueue: LinkedBlockingQueue
生产者端和消费者端分别采用了独立的锁来控制数据同步, 这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能
效果总结:
最大线程数和核心线程数一致。 即:从0开始新建,当达到固定值后不新建,也不销毁线程对象(销毁数量:最大线程数 - 核心线程数)

newScheduledThreadPool(int)

构造方法参数:
corePoolSize: 参数值
maximunPoolSize: int的最大值
keepAliveTime: 0秒
workQueue: DelayedWorkQueue
DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中放入数据的操作永远不会被阻塞,而只有获取数据的操作才会被阻塞
效果总结:
指定核心线程数,超出核心线程数的任务放入到延迟队列中,线程空闲立即被回收

newWorkStealingPool()

该方法是Java 8 新增的一个方法。底层使用的是ForkJoinPool类

ForkJoinPool介绍

ForkJoinPool是Java7新增的线程池类型。是ThreadPoolExecutor的兄弟类
工作原理(工作窃取算法):把一个Thread 分叉(fork)成多个子线程。让多个子线程执行本来一个线程应该执行的任务。最后把多个线程执行结果合并

如果在分叉后一个线程执行完成,另外的线程还没有结束,会重双端队列中尾部处理任务,另一个线程从头部取任务,防止出现线程竞争


所有任务都是异步的。不会阻塞主线程,主线程不用等到线程池任务结束在关闭

线程池代码演示

创建了固定长度为10的线程池
每个线程任务是输出随机数后休眠500毫秒
每次10个核心线程执行任务

public class Threadpool {public static void main(String[] args) {//创建线程池, 核心线程数和最大线程数都为10, 一次最多有10个线程工作ExecutorService executorService = Executors.newFixedThreadPool(10);//分配100个任务for (int i = 0; i < 50; i++) {//将任务交给线程池, 每次10个核心线程并行执行executorService.submit(new Runnable() {@Overridepublic void run() {try {int i1 = new Random().nextInt(11);System.out.println(Thread.currentThread().getName()+": "+i1);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});}//关闭线程池, 即使执行了关闭线程池, 也会等待线程池中的任务执行完才会关闭executorService.shutdown();}
}

JUC中的AtomicInteger原子类

原子性介绍

原子性:数据操作是整体,整体只有成功或失败,不允许出现部分成功部分失败
例如:
num++在多线程下执行是不安全的, num++可以分解为

  1. 读取num的值(从主内存读取)
  2. 对num+1(副本内存操作)
  3. 把结果赋值给num(更新主内存)
    所以说num++是不具备原子性的
    如果希望num++具备原子性,可以把num++的三个步骤看做一个不可分的整体。只要具备了原子性,就一定是线程安全的

JUC中原子类

使用AtomicInteger保证线程安全

原始方式: 使用synchronized同步锁解决

public class Demo07 {static int a = 0;public static void main(String[] args) {//创建线程池ExecutorService executorService = Executors.newFixedThreadPool(5);//创建五个任务for (int i = 0; i < 5; i++) {//五个核心线程处理任务executorService.submit(new Runnable() {@Overridepublic void run() {//保证数据的安全性, 使用同步锁synchronized ("锁"){for (int i1 = 0; i1 < 10000; i1++) {a++;}}}});}try {Thread.sleep(2000);System.out.println(a);} catch (InterruptedException e) {e.printStackTrace();}}
}

原子类方式: 使用AtomicInteger原子类

public class Demo08 {public static void main(String[] args) {//使用int原子类, 初始值设置为0AtomicInteger atomicInteger = new AtomicInteger(0);//创建线程池ExecutorService executorService = Executors.newFixedThreadPool(5);//创建五个任务for (int i = 0; i < 5; i++) {//五个核心线程处理任务executorService.submit(new Runnable() {@Overridepublic void run() {for (int i1 = 0; i1 < 10000; i1++) {//先获取再+1int i2 = atomicInteger.incrementAndGet();System.out.println(Thread.currentThread().getName()+": "+i2);}}});}try {Thread.sleep(2000);System.out.println("总和:"+atomicInteger.get());} catch (InterruptedException e) {e.printStackTrace();}}
}

原子类AtomicInteger方法介绍

public class Demo09 {public static void main(String[] args) {//创建原子类AtomicInterger, 设置初始值为1AtomicInteger atomicInteger = new AtomicInteger(1);ExecutorService executorService = Executors.newFixedThreadPool(5);for (int i = 0; i < 1; i++) {//五个核心线程处理任务executorService.submit(new Runnable() {@Overridepublic void run() {for (int i1 = 0; i1 < 1; i1++) {System.out.println("------");//先+1, 再获取int i2 = atomicInteger.incrementAndGet();System.out.println(i2);//先获取, 再+1int andIncrement = atomicInteger.getAndIncrement();System.out.println(andIncrement);//先-1, 再获取int i3 = atomicInteger.decrementAndGet();System.out.println(i3);//先获取, 再获取int andDecrement = atomicInteger.getAndDecrement();System.out.println(andDecrement);//先+指定的值, 再获取int i4 = atomicInteger.addAndGet(100);System.out.println(i4);//先获取, 再+指定的值int andAdd = atomicInteger.getAndAdd(200);System.out.println(andAdd);//获取值int i5 = atomicInteger.get();System.out.println(i5);System.out.println("======");}}});}}
}

原子类AtomicInteger底层实现

原子类AtomicInteger底层使用的是volatile和cas

Volatitle(面试题)

在多线程环境下,volatile 关键字可以保证共享数据的可见性,有序性, 但是并不能保证对数据操作的原子性(数据操作是整体,整体只有成功或失败,不允许出现部分成功部分失败)。也就是说,多线程环境下,使用 volatile 修饰的变量是线程不安全的

可见性

内存可见性是指当一个线程修改了某个变量的值,其它线程总是能立即知道这个变量的变化。也就是说,如果线程 A 修改了共享变量 V 的值,那么线程再B 在使用 V 的值时,能立即读到 V 的最新值(排除B正在用使用变量V)

可见性原理

现在CPU很少有单核CPU,都是双核、双核四线程、四核、四核八线程甚至更好的CPU。 CPU 的速度是极高的,如果 CPU 需要存取数据时都直接与内存打交道,一定没有直接操作自己的高速缓存效率高,所以,为了提高处理速度,CPU 不直接和主内存进行通信,而是在 CPU 与内存之间加入高速缓存,它们比直接操作主内存的速度高的多
由于 CPU 与内存之间加入了缓存,在进行数据操作时,先将数据从内存拷贝到缓存中,CPU 直接操作的是缓存中的数据。但在多处理器下,将可能导致各自的缓存数据不一致(这也是可见性问题的由来),为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,而总线嗅探机制是实现缓存一致性的常见机制

嗅探机制工作原理

每个处理器通过监听在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从主内存中把数据读到处理器缓存中

有序性

什么是指令重排?
为了提高性能,编译器和处理器常常会对指令做重排序。
一般重排序可以分为如下三种类型:

  1. 编译器优化重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
    数据依赖性:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑
    从 Java 源代码到最终执行的指令序列,会分别经历下面三种重排序:


有序性原理

使用 volatile 修饰变量时,Java 编译器在生成字节码时,会在指令序列中插入内存屏障(CPU处理器的指令)来禁止CPU处理器重排序

CAS算法(面试题)

CAS算法介绍

CAS( Compare And Swap)算法,比较和交换算法。
CAS不需要和synchronized一样让对象具有独立性、互斥性保持线程安全。而是一种无锁也可以保证线程安全的算法。
理解:
给定代码:
//num++
num=num+1;
多个线程同时执行这段代码
synchronized: 一次只能有一个线程操作
CAS算法: 实现思路
线程开启时候,每个线程在主内存中拷贝一个变量副本, 每个线程操作自己的副本
1.首先获取到num在主内存中值, 存储为一个副本
2.然后对num的值+1
3.修改主内存中num值时,会比较主内存中的num值是否和副本中值相同
a. 如果主内存中num值和副本值相同,把主内存num值更新为新的值
b. 如果内存中num值和副本值不同,会从第1步重新开始执行

优点

保证数据操作的原子性,保证了线程是安全的
这个算法相对synchronized是比较“乐观的”,它不会像synchronized一样,当一个线程访问共享数据的时候,别的线程都在阻塞。synchronized不管是否有线程冲突都会进行加锁。由于CAS是非阻塞的,它死锁问题天生免疫,并且线程间的相互影响也非常小,更重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,所以它要比锁的方式拥有更优越的性能

缺点

多线程操作时每次修改值时,可能多次出现内存值和副本值不同的情况,需要循环执行多次
可能出现ABA问题

ABA问题

所谓ABA问题,其实用最通俗易懂的话语来总结就是狸猫换太子
比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题
比如有两个线程A、B:

  1. 主内存中变量为 V=3
    1.两个线程A, B, 都拷贝了主内存中的变量 E=3
    2.A线程执行到N=V+0, 即N=3 此时A线程挂起
    3.B线程修改原值为N=V+1,即N=4,
    4.然后B觉得修改错了,然后再重新把值修改为3;
    5.A线程被唤醒,执行this.compareAndSwapInt()方法,发现这个时候主内存V的值等于线程A中E的值, 都为3,(但是却不知道B曾经修改过), A线程修改成功。
    6.尽管线程A的CAS操作成功,结果也没有问题, 但不代表就没有问题, V的值是发生过改变的
    a)有的需求,只注重结果,结果一直就可以, 可以不用考虑ABA的问题
    b)但是有的需求,需要注重过程,必须考虑ABA的问题

解决ABA问题

为了解决ABA问题,一般都会在操作时设置一个int类型版本号(version),每次对内存中变量操作后都让版本号加1。当需要修改变量时,除了比较副本中值和内存值以外,还需要比较版本号是否相同。JDK中AtomicStampedReference就是这种方式,提供了全局int属性

源码分析AtomicInteger原子类

底层实现

原子类AtomicInteger底层使用的是volatile和cas
volatitle保证了可见性, 有序性, cas保证了原子性
CAS算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。
当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即时没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理

源码重点

Jdk8


jdk11

public class AtomicInteger extends Number implements java.io.Serializable {/** 序列化id*   序列化时和反序列化时都使用该Id* */private static final long serialVersionUID = 6214790243416807050L;/** 设置为使用Unsafe.compareAndSwapInt进行主内存的更新*   属于底层的操作, 直接操作主内存. 不建议手动调用* */private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();/** value属性的属性偏移量(理解: value属性的引用地址)*   参数一: AtomicInteger类对象*   参数二: 属性名*   根据类对象和属性名就可以获取该属性的属性对象* */private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");/** value由volatile修饰, 可以保证可见性和有序性* */private volatile int value;/*有参构造方法* 使用给定的初始值创建AtomicInteger*  即value = initialValue;*/public AtomicInteger(int initialValue) {value = initialValue;}/*无参构造方法* 创建一个初始值为0的AtomicInteger实例*  即value=0;*/public AtomicInteger() {}/** 获取当前value的属性值*/public final int get() {return value;}/** 设置当前value的属性值*/public final void set(int newValue) {value = newValue;}/** 原子级设置为给定值并返回旧值*/public final int getAndSet(int newValue) {return unsafe.getAndSetInt(this, valueOffset, newValue);}/** 以原子方式将值设置为给定的更新值*  @param expect 预期值*  @param update 新值*  @return 成功返回true 返回false表明实际值不等于预期值,设置失败*/public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}/** 以原子方式将值设置为给定的更新值*  <p><a href="package-summary.html#weakCompareAndSet"可能会失败

JUC和线程池的详细讲解相关推荐

  1. Java自带的线程池ThreadPoolExecutor详细介绍说明和实例运用

    Java 5 开始,Java 提供了自己的线程池.线程池就是一个线程的容器,每次只执行额定数量的线程. java.util.concurrent.ThreadPoolExecutor 就是这样的线程池 ...

  2. linux进程池 自动增长,linux下C 线程池的原理讲解和代码实现(能自行伸缩扩展线程数)...

    什么线程池,为什么要使用线程池?下面是一个比喻. 阶段一.一个医院,每天面对成千上万的病人,处理方式是:来一个病人找来一个医生处理,处理完了医生也走了.当看病时间较短的时候,医生来去的时间,显得尤为费 ...

  3. JUC(十)-线程池-ThreadPoolExecutor分析

    ThreadPoolExecutor 应用 & 源码解析 文章目录 ThreadPoolExecutor 应用 & 源码解析 一.线程池相关介绍 1.1 为什么有了JDK提供的现有的创 ...

  4. 四种常用线程池及自定义线程池参数详细分析

    文章目录 一.什么是线程池 二.常用的更方便的Executors工厂方法 三.自定义线程池 四.缓冲队列BlockingQueue 五. 排队的三种一般策略 六.拒绝策略 一.什么是线程池 线程池(英 ...

  5. Java线程池ThreadPoolExecutor参数讲解、实例助记 保证你过目不忘

    ThreadPoolExecutor构造器的参数说明 参数一:指定线程池的核心线程数量(核心线程,长久不死亡): corePoolSize 不能小于0 参数二:指定线程池可支持的最大线程数: maxi ...

  6. 进程 线程 协程 详细讲解

    进程 第一,进程是一个实体.每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region).数据区域(data region)和堆栈(stack region).文本区域存储处理器 ...

  7. JUC探险-17、线程池

    文章目录 一.线程:   ①线程的创建     如果同时继承了Thread类且实现了Runnable接口,会怎样执行?   ②线程状态   ③线程状态的转换     线程状态的转换操作       1 ...

  8. 2、线程池篇 - 从理论基础到具体代码示例讲解(持续更新中......)

    前言 暂无. 一.线程篇 有关线程部分的知识整理请看我下面这篇博客: 1.线程篇 - 从理论到具体代码案例最全线程知识点梳理(持续更新中-) 二.线程池基础知识 线程池优点 他的主要特点为: 线程复用 ...

  9. 如图两道面试题,顺便深入线程池,并连环17问

    这两面试题是基友朋友最近去面滴滴遇到的,今天就借着这两面试真题来深入一波线程池吧,这篇文章力求把线程池核心点和常问的面试点一网打尽,当然个人能力有限,可能会有遗漏,欢迎留言补充! 先把问题列出来,如果 ...

  10. Java多线程系列(五):线程池的实现原理、优点与风险、以及四种线程池实现

    为什么需要线程池 我们有两种常见的创建线程的方法,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口.但是我们创建这两种线程在运行结束后都会被 ...

最新文章

  1. 南京邮电大学网络攻防训练平台(NCTF)-异性相吸-Writeup
  2. 过早扩张、未经检验的技术,创业公司最易跳入哪些致命陷阱?
  3. QQ聊天文字背影图片拉伸方法
  4. CSS pointer-events属性的使用
  5. 大学学了一个学期的 C 语言,我们应该明白哪些知识点?别像没学一样!
  6. 【会议/期刊】中科院推荐计算机领域人工智能方向会议和期刊列表
  7. POJ - 1251(最小生成树.krustal)
  8. sentinel 时间窗口_Sentinel 实战-规则持久化
  9. 异常之【You have an error in your SQL syntax】
  10. Spring boot集成Swagger3
  11. 前后端分离的思考与实践(六)
  12. css中hack是什么
  13. android视频壁纸源码,Android 视频壁纸
  14. photoshop2022更新,新版PS新增了哪些功能Mac/win
  15. 基于cnn的人脸识别_人脸识别技术:从传统方法到深度学习
  16. Linux基金会:开源技术不受制于《美国出口管制条例》EAR 限制,可自由使用
  17. YouTube和Twitch上的流媒体之间有何区别?
  18. 电脑硬盘不小心格式化了文件怎么恢复,手把手教你变恢复高手
  19. redis底层数据结构(redis底层存储结构、源码分析)
  20. 重庆理工大学计算机考研资料汇总

热门文章

  1. 【答题卡识别】基于matlab GUI hough变换答题卡成绩统计(带面板)【含Matlab源码 1828期】
  2. 毕设题目:Matlab人脸识别
  3. 【三维路径规划】基于matlab A_star算法无人机三维路径规划(起终点障碍物可设置)【含Matlab源码 1321期】
  4. 【优化选址】基于matlab禁忌搜索算法求解基站选址优化问题【含Matlab源码 440期】
  5. 【行为识别】基于matlab轨迹法行为识别【含Matlab源码 375期】
  6. 可行性研究报告计算机,台式计算机项目可行性研究报告范文.doc
  7. 论文计算机在管理会计中的应用,浅论管理会计在企业中的应用
  8. 二进制,逆向工程,深入理解计算机系统
  9. java 打牌游戏_java代码-----实现4个人打牌游戏的相关代码。线程
  10. linux内核 semaphore,Linux内核参数信号量semaphore设置