多线程juc
demo在 springboot-demo/demo
服务器核
单核:某一个时间点上只能做一件事
双核: 可以做两件
并行与并发
并发是一个处理器在一个时间段同时处理多个任务。其实并不是同时进行,是cpu在切换执行。会抢占资源
一台咖啡机排排两列队。
并行是多个处理器或者多核的处理器各自同时在执行自己的任务。不会抢占资源
两台咖啡机各自排一列队
线程、进程、管程
进程:资源分配和调度的基本单位,java程序运行后就是一个进程,有一个pid
线程:进程中的实际运作单位,是操作系统运算调度的最小单位。一般一个进程有多个线程在运行
管程:monitor监视器,也就是锁,每个对象都有
线程状态图

创建线程
- 继承Thread类
- 实现Runnable接口
都是重写run方法,start 方法用于启动线程,调用的是native start0()。
- new Thread传入Runnable匿名内部类。
线程执行流程:
取决于操作系统的CPU,run方法是由操作系统底层调用。由CPU给线程分配时间片,分到了再执行。分配的时间片能把线程执行完就执行完,不能执行完就执行多少算多少,时间片用完就会让出执行权重新竞争时间片。
时间片用完了,保存执行状态以及恢复叫做上下问切换。
线程池
更多的我们不是创建线程,而是线程池。ThreadTest类
线程池(ThreadPoolExecutor)
- 池的作用
- 限定线程个数,防止线程过多
- 节省开销
- 创建过程
- 创建出来时,线程池的线程数是0。
- 执行请求(submit()),如果正在运行的线程数量小于核心则创建新线程执行。即使核心有空闲的也是
- 大于等于核心,任务放入队列
- 队列满了,但是还没达到最大,继续创建新线程。达到了就执行拒绝策略
- 线程空闲超过存活时间,大于核心的线程数会停掉。核心的默认一直存活。设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
- 参数
- corePoolSize:核心线程数
- maxPoolSize:最大线程数
- Keep-alive times:线程存活时间
- unit:时间单位
- workQueue:队列
- ThreadFactory:线程创建工厂,非必填
- RejectedExecutionHandler:拒绝策略,出现原因:1. 线程池已经被关闭;2. 达到最大线程时还需要创建线程。
- java提供:
终止:默认直接报错
抛弃:不做任何处理 直接抛弃任务
抛弃最旧的:队列的第一个抛掉,再尝试提交,用PriorityBlockingQueue会把优先级最高的抛掉
调用者运行:既不抛弃任务也不抛出异常,线程池饱和后将由调用线程池的主线程自己来执行任务,因此在执行任务的这段时间里主线程无法再提交新任务。
- java提供:
线程池(Executors)弃用
其实它很多api最终也是用的ThreadPoolExecutor,只是默认了一些参数
- newSingleThreadExecutor:创建一个执行有序 并且失败重启的单一线程
- newCachedThreadPool:创建缓存线程池(根据当前需要进行创建)适用于短时异步任务
- newFixedThreadPool:创建固定大小的线程池
- newScheduledThreadPool:定时任务的线程池,支持定时及周期性的执行任务,也是quarz的底层
弃用原因:阿里巴巴开发手册说:线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式
1、例如newFixedThreadPool(N),固定了N个线程,队列使用的是LinkedBlockingQueue,源码显示是Integer.MAX_VALUE。如果出现阻塞,队列里面的任务会越来越多,最终oom。
2 使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。
Queue
-
无界队列:队列大小无限制,LinkedBlockingQueue,
问题:当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM,Executors.newFixedThreadPool 就是这个。 -
有界队列:先进先出:ArrayBlockingQueue
-
优先级队列:PriorityBlockingQueue,对象实现conparable接口,优先级由任务的Comparator决定
-
同步移交队列:SynchronousQueue,要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列
大队列、小线程池:降低CPU使用率、操作系统资源和上下文切换开销,但是可能降低系统吞吐量。 小队列、大线程池:CPU使用率较高,理论上可以提升系统吞吐量。太大可能增加上下文切换时间,也会降低效率。 视服务器资源可调优。
future接口
当我们需要线程异步执行的结果时,需要提交Callable接口,返回future。
- 阻塞式获取:主线程会一直阻塞,直到子线程执行完毕。
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("主线程执行中");
Future<Object> future = executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
Thread.sleep(5000);
System.out.println("子线程执行完毕");
return "子线程执行完毕~";
}
});
// System.out.println(future.get()); //阻塞
System.out.println("主线程执行完");
}
- 使用CompletableFuture实现类,非阻塞式获取
public static void main(String[] args) {
System.out.println("主线程执行中");
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "子线程执行完毕~";
}, executor).whenComplete((v, e) -> {
if (e == null){
System.out.println("子线程: " + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:" + e.getCause() + "\t" + e.getMessage());
return null;
});
System.out.println("主线程执行完");
}
AQS
- abstractQueuedSynchronizer抽象队列同步器。一个先进先出的抽象队列 状态值state表示锁是否占用。将线程都封装进一个一个的node节点排队。是juc体系的基石,主要用于解决锁分配给“谁”的问题。
- 基石:提供多线程的基础api,线程抢占锁、排队的实现。比如ReentrantLock有一个内部类Sync,它继承了AbstractQueuedSynchronizer。ReentrantLock的公平锁 非公平锁的实现都在aqs里面
三个辅助类
CountDownLatchDemo.java
- CountDownLatch:减少计数,减少至0执行。可以实现等线程结束了,主线程才执行。
- CyclicBarrier:循环栅栏,有指定线程数等待再执行
- Semaphore:信号灯,抢到了semaphore就执行。可以实现指定线程数目访问指定代码,例如redis缓存击穿时,所有请求都会打到数据库,可以通过这种方式限制指定数量的线程去访问数据库。PublicController “get/data”
问:怎么等所有的子线程结束再结束主线程
1、CountDownLatch
2、future.get()阻塞
3、t.join()
4、t.isAlive() == true){//t.getState() != State.TERMINATED这两种判断方式都可以
https://www.cnblogs.com/lixin-link/p/10998058.html
threadlocal
ThreadLocalDemo.java
作用
为线程提供局部变量。在每个线程中都有独立拷贝,不会被其他线程干扰。
api
- withInitial
- set get
- remove:释放数据,否则即使ThreadLocal = null数据也不会自动回收造成内存泄露,解释见下。
Thread threadLocal ThreadLocalMap
- 创建一个thread线程,使用threadLocal,它实际使用的是threadLocal里面的静态内部类ThreadLocalMap,这个map就是真正存放数据的地方。get/set都是在当前线程的ThreadLocalMap里面操作。
- map的entry是弱引用(不论内存是否充足,gc回收都会回收掉它)。如果是强引用,外界是通过ThreadLocal来对ThreadLocalMap操作,ThreadLocal回收后因为ThreadLocalMap下的key-value是强引用,它不会被回收,造成内存泄露。
- 如果是弱引用,可以保证key指向的value能被回收。ThreadLocal被回收时,可能会在map里产生key为null的情况,对于这种就无法回收,所以还需要手动掉remove。
- 使用弱引用+remove来达到不产生内存泄露
关键字
- sleep
- Thread类的静态方法,使线程暂停一会,自动恢复
- 交出CPU,让CPU去执行其他的任务,不会释放锁。
- interrupt中断会报错,需要在try里面再中断一次
- wait
- Object类的方法,让当前线程由“运行状态”进入“等待(阻塞)状态”,直到其他线程调用notify(只随机唤醒一个 wait 线程)或notifyAll(唤醒所有 wait 线程)或者达到指定的等待时间。
- 释放同步锁
- interrupt可以中断
二者的区别:是否释放锁,方法来源、入参不同
-
中断:不是真正的停掉一个线程,线程只能自己停止,只是设置一个状态,是否停止由本线程决定InterruptDemo.java
- interrupt()
设置线程的中断状态是true。中断的线程运行过程中先判断中断标识,为true就不执行。
打断后状态是true,线程执行完了又变回false(对于空闲的线程这个方法没有意义) - isInterrupted
返回中断状态 - static interruptes
返回中断状态,并重新设置为false
- interrupt()
-
yield():
- “运行状态”进入到“就绪状态”,重新竞争cpu执行权但是并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权
- 不会释放锁。
-
join()
- 等待该线程终止。加在主线程的话,主线程会等待其他线程结束再结束。加在其他线程,会等待join的线程先执行。

- 等待该线程终止。加在主线程的话,主线程会等待其他线程结束再结束。加在其他线程,会等待join的线程先执行。
-
setDaemon/isDaemon
设置为守护线程,都是的话,jvm退出。
是个服务线程,准确地来说就是服务其他的线程。Java 中垃圾回收线程就是特殊的守护线程。
线程通信
1、ThreadDemo1 2 3
线程执行顺序不定,可以通过线程间通信实现按顺序执行。通过wait/notifyAll、await/signalAll。
LockSupport也可以实现并且不用考虑先等待再唤醒。
2、Interrupt
一个线程中断其他线程
1081





