内容回顾
1、并发容器类
-
ArrayList
-
HashSet
-
HashMap:Collections工具类、ConcurrentHashMap
-
ConcurrentHashMap底层
2、JUC常见工具类
-
减少计算
-
循环栅栏
-
信号量
3、Callable接口
-
Callable接口如何创建线程:FutureTask
-
FutureTask特点
-
callable接口与runnable接口的区别
4、阻塞队列
5、线程池
-
创建线程池方式:工具类和ThreadPoolExecutor
-
ThreadPoolExecutor 七个参数
-
核心线程数设置规则
今天内容
1、线程池(重要)
第一,线程池优点
第二,线程池创建方式
第三,ThreadPoolExecutor七个参数
第四,线程池执行流程
第五,五种拒绝策略
第六,为什么不使用Executors工具类创建线程池
线程池底层工作流程(背)


问题:线程池工作原理:
第一个层面:底层使用对象ThreadPoolExecutor,这个对象里面传递七个参数,把七个参数说一遍
第二个层面:工作流程
拒绝策略
- 线程池支持五种拒绝策略
-
AbortPolicy(默认):直接抛出异常 ( ThreadPoolExecutor自带)
-
CallerRunsPolicy:回到调用者,"调用者运行"一种调节机制。 ( ThreadPoolExecutor自带)
-
DiscardOldestPolicy:抛弃队列中等待最久的任务 ( ThreadPoolExecutor自带)
-
DiscardPolicy:默默地丢弃无法处理的任务。 如果允许任务丢失,这是最好的一种策略
( ThreadPoolExecutor自带)
-
自定义拒绝策略
new ThreadPoolExecutor.AbortPolicy() //丢弃任务并抛出异常
new ThreadPoolExecutor.CallerRunsPolicy() //由调用线程处理该任务,谁调用谁处理
new ThreadPoolExecutor.DiscardOldestPolicy() //丢弃队列中等待最久的任务,添加新任务
new ThreadPoolExecutor.DiscardPolicy() //也是丢弃任务,但是不抛出异常。
//自定义拒绝策略
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定义拒绝策略");
}
}
自定义线程池
概述
-
创建线程池有两种方式:第一种使用工具类Executors ,第二种方式new ThreadPoolExecutor传递七个参数
-
实际应用中都使用第二种方式ThreadPoolExecutor实现,自定义线程池
-
使用Executors工具类创建线程池,支持线程数最大值是Integer.max_value,很容易造成大量线程堆积,导致OOM问题
如何自定义
package com.atguigu.juc.pool;
import java.util.concurrent.*;
public class CustomizeThreadPoolDemo {
public static void main(String[] args) {
//ThreadPoolExecutor
ExecutorService poolExecutor = new ThreadPoolExecutor(2,
5,
3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 1; i <=9; i++) {
poolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+ "执行了业务逻辑");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
poolExecutor.shutdown();
}
}
}
2、多线程高并发底层原理(面试)
Java内存模型
工作流程
-
jmm包含主内存和本地内存(工作内存)
-
在主内存放共享变量
-
每次线程都有自己的私有本地内存(工作内存)
-
比如线程A操作共享变量,线程不能直接操作主内存里面共享变量,需要通过自己本地内存共享变量副本才能进行操作

Java内存模型的三大特性
- 原子性
-- 不可分割,通俗我在操作时候,别人只能等待,别人不能插队的
a = 1;
a = a+1;
- 有序性
-- 防止指令重排
- 可见性
-- 多个线程操作同一个共享变量,多个线程之间可以看到不同线程里面本地内存修改的数据
volatile关键字
- volatile关键字具备可见性 和 有序性,但是不具备原子性
验证可见性
package com.atguigu.juc.jmm;
public class VolatileDemo {
private static volatile Integer flag = 1;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程工作内存读取到的flag的值:" + flag);
while(flag == 1){}
System.out.println("子线程工作内存读取到的flag的最新的值..." + flag);
}
}).start();
Thread.sleep(500);
flag = 2; //主线程修改flag的值
System.out.println("我是主线程工作内存flag的值:" + flag);
}
}
验证有序性
通俗,哪个线程先修改,哪个线程就首先同步主内存
如果不能保证有序性,发生指令重排
保证有序性,使用volidate关键字实现
static volatile int a,b;
验证原子性
-
volidate不具备原子性
-
如果保证原子性,可以加锁实现
class Data {
private volatile Integer number = 0;
// +1
public synchronized Integer incr() {
return ++number;
}
}
public class Demo {
public static void main(String[] args) {
Data dataOne = new Data();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
System.out.println(dataOne.incr());
}).start();
}
}
}
CAS
引入
-
为了保证原子性,使用加锁解决
-
实现效果:不加锁情况下,保证原子性
-- 在juc里面有包java.util.concurrent.atomic,使用这个包提供类保证变量的原子性
//A small toolkit of classes that support lock-free thread-safe programming on single variables.
//是小型工具包,使用这个工具包实现变量不加锁情况下实现线程安全(原子性)
class Data {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public Integer incr() {
return atomicInteger.incrementAndGet();
}
}

public static void main(String[] args) {
//乐观锁原理:比较和交换
AtomicInteger i = new AtomicInteger(1);
//比较当前值initialValue 1 和预期值expectedValue 1,相等,则将当前值换为新值
System.out.println("第一次更新:" +
i.compareAndSet(1, 200));
System.out.println("第一次更新后i的值:" + i.get()); //200
System.out.println("第二次更新:" +
i.compareAndSet(1, 300));
System.out.println("第二次更新后i的值:" + i.get()); // 200
System.out.println("第三次更新:"
+ i.compareAndSet(200, 300));
System.out.println("第三次更新后i的值:" + i.get()); // 300
}
CAS理论
-
CAS (Compare-and-Swap)比较并交换, 是一种原子操作指令,用于解决多线程并发安全问题。CAS 操作是一种乐观锁算法。
-
乐观锁:核心是使用版本号控制
-
CAS底层使用类Unsafe,这个类final类,构造私有的
-
在Unsafe类有CAS比较并交换方法
// U的compareAndSetInt方法:对象、对象的属性地址偏移量、预期值、修改值
public final native boolean compareAndSetInt(Object o, long offset,int expected, int x);
//拿着传入旧值,到属性地址里面比较是否相同,如果相同的,使用新值到地址替换旧值
- CAS有缺陷:
-- 开销大
-- 不能保证代码块原子性,只能保证变量原子性
-- 默认不能解决ABA问题,更新时候携带版本号 AtomicStampedReference
AQS
AbstractQueuedSynchronizer 抽象队列同步器简称AQS,是整个java.util.concurrent包的核心。JUC下的Lock(ReentrantLock、ReentrantReadWriteLock等)以及并发工具类(Semaphore、CountDownLatch、CyclicBarrier等)就是通过AQS实现的。
AQS底层原理:
1、AQS使用volatile关键字定义一个成员变量state,state表示锁是否已被持有
2、AQS使用CAS机制对state进行原子操作,也就是对state的值进行修改。
如果state的值为0,表示没有加锁,线程抢到资源进行加锁,加锁是将state的值设置为1,返回成功获取到锁。如果state的值为1,表示当前资源有锁,如果释放锁,把state值修改为0。
3、公平锁:有多个线程抢占资源,肯定有一个线程抢到,对当前操作加锁,其他没有抢到线程再尝试抢资源,如果多次都没有抢到,进入队列进行排队等待
4、非公平锁:有多个线程抢占资源,肯定有一个线程抢到,对当前操作加锁,其他没有抢到线程再尝试抢资源,如果多次都没有抢到,进入队列进行排队等待。但是,比如现在有一个新线程请求获取当前锁,恰巧当前持有锁线程释放了,新线程立刻获取当前锁(插队)
比如现在有一个新线程请求获取当前锁,但是当前持有锁线程一直没有释放,新线程进入队列等待
自旋锁
public AlbumInfo getAlbumInfo(Long id) {
//创建锁
Lock lock = new ReentrantLock();
try {
boolean tryLock = lock.tryLock(3,TimeUnit.SECONDS);
if(tryLock) { //true 获取当前锁
//查询数据库,返回对象
AlbumInfo albumInfo = getData(id);
return albumInfo;
} else { //false 获取当前锁失败
//////////////////////////////////
//自旋
return getAlbumInfo(id);
}
}catch (Exception e) {
}finally {
//解锁
lock.unlock();
}
}
3、补充-JUC并行操作
实际应用场景
-
把项目中串行操作 修改 并行操作,提高执行效率
-
底层:有几个操作,创建几个线程,让这些线程一起执行,最终再进行汇总
如何实现
-
之前学习过对象FutureTask,未来任务,这个对象有特点:异步计算(并行)
-
实际中,使用juc并行操作工具类进行实现 - CompletableFuture
CompletableFuture做并行操作考虑以下两点:
-
第一,结合具体业务,确定当前做几个操作
-
第二,当前这些操作之间是否有关系

public class Demo001 {
public static void main(String[] args) {
//CompletableFuture.runAsync() void
//CompletableFuture.supplyAsync() 有返回值
//objectCompletableFuture.thenAcceptAsync() 在一个线程执行完成之后,执行下一个任务
//CompletableFuture.allOf().join()
//创建自定义线程池
ExecutorService poolExecutor = new ThreadPoolExecutor(2,
5,
3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Map<String,String> map = new HashMap<>();
//当前三个操作
//1 获取用户id
CompletableFuture<Integer> completableFuture1 =
CompletableFuture.supplyAsync(() -> {
map.put("1","a");
return 1;
},poolExecutor);
//1 和 2 有关系
// * 2 操作如何执行,等待1操作执行完成之后返回用户id
// * 2 等待 1执行完成之后才能执行
//2 根据用户id获取用户信息
CompletableFuture<Void> completableFuture2 =
completableFuture1.thenAcceptAsync((value) -> {
System.out.println("value:"+value);
map.put("2","b");
},poolExecutor);
//3 查询所有商品
CompletableFuture<Void> completableFuture3 = CompletableFuture.runAsync(() -> {
map.put("3", "c");
},poolExecutor);
//4 等待所有子线程执行完成之后,进行汇总
CompletableFuture.allOf(completableFuture1,
completableFuture2,
completableFuture3).join();
System.out.println(map);
}
}