学习JUC和多线程
JUC
JMM

原子性,可见性与有序性
原子性
| 原子性 | 解释 |
|---|---|
| 概念 | 执行线程对共享资源发生写操作,其他线程只能看到结果,而中间过程看不到 |
| 例子 | 银行转账流程中,A账户减少了100元,那么B账户就会多100元,这两个动作是一个原子操作。我们不会看到A减少了100元,但是B余额保持不变的中间结果 |
| 实现 | 加锁;CAS;基本和引用类型中除了Long和Double的写操作都是原子类型 |
可见性
| 可见性 | 解释 |
|---|---|
| 概念 | 一个线程对于共享变量的更新,对于后续访问该变量的线程是否可见的问题。 |
| 例子和实现 | 请看volatile的模拟可见性代码 |
有序性
| 有序性 | 解释 |
|---|---|
| 概念 | 计算机在执行程序时,为了提高性能,存在编译器和处理器对指令重排的问题 |
| 例子 | 如下的Demo1、Demo2 |
| 实现 | 使用volatile |

/*
1 分配对象的内存空间(堆上)
2 初始化对象
3 设置instance指向刚分配的内存地址
第二步和第三步可能会发生重排序,导致引用型变量指向了一个不为null但是也不完整的对象。(在多线程下的单例模式中,我们必须通过volatile来禁止指令重排序)
*/
Instance instance = new Instance() public class ReSortDemo {
int a;
boolean flag = false;
// 重排序demo1
public void method1() {
// 这里可能出现指令重排问题:
// 线程一:编译器优化使得,flag先为true,然后线程二走method2进去,a=0+1=1,出现线程不安全的问题
a = 1;
flag = true;
}
public void method2() {
if (flag) {
a = a + 1;
System.out.println("最终的a:" + a);
}
}
// 重排序demo2:
void resetSort() {
int a = 1; // 语句1
int b = 2; // 语句2
int x = a + 1;// 语句3
int y = a * a;// 语句4
/* 指令重排:编译器优化、会将语句顺序打乱
重排可能性:1234、1324、2134
但是4是不能在1之前的,因为编译器优化会保证数据结果之间的依赖性
*/
}
} 乐观锁和悲观锁
| 乐观锁 | 悲观锁 | |
|---|---|---|
| 概念 | 读数据时,其他线程不会修改;写数据时再判断是否被修改 | 读写数据时,其他线程都会修改 |
| 加锁 | 写时先读出当前版本号,然后加锁 | 每次读写都加锁 |
| 原理 | CAS比较并交换 | AQS抽象队列同步器 |
Synchronized
概念:synchronized是关键字,是一个独占悲观锁,也是可重入锁。
底层原理:
- 进入时,执行monitorenter,将计数器+1,释放锁monitorexit时,计数器-1
- 当一个线程判断到计数器为0时,则当前锁空闲,可以占用;反之,当前线程进入等待状态
作用范围:
- 作用成员变量和非静态方法,锁的是对象的实例=this对象
- 作用静态方法,锁的是Class实例,因为静态方法需要Class而不属于对象
- 作用代码块,锁的是所有代码块中的对象
Reentrant
概念:继承里Lock接口,是一个可重入独占锁,默认是非公平锁。
底层原理:通过AQS来实现锁的获取和释放
如何避免死锁:
- 晌应中断
- 可轮询锁
- 定时锁
public class InterruptDeadLock {
private ReentrantLock lock1 = new ReentrantLock();
private ReentrantLock lock2 = new ReentrantLock();
public Thread lock1() {
Thread t = new Thread(() -> {
try {
lock1.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + ",执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.println(Thread.currentThread().getName() + ",finally退出完毕");
}
});
t.start();
return t;
}
public Thread lock2() {
Thread t = new Thread(() -> {
try {
lock2.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + ",执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.println(Thread.currentThread().getName() + ",finally退出完毕");
}
});
t.start();
return t;
}
public static void main(String[] args) {
long time = System.currentTimeMillis();
InterruptDeadLock interruptDeadLock = new InterruptDeadLock();
Thread t1 = interruptDeadLock.lock1();
Thread t2 = interruptDeadLock.lock2();
// 出现死锁,t2就开始释放锁
while (true) {
if (System.currentTimeMillis() - time >= 3000) {
t2.interrupt();
}
}
}
} java.lang.InterruptedException Thread-0,执行完毕 Thread-0,finally退出完毕 Thread-1,finally退出完毕
volatile
| 名称 | 原子性 | 可见性 | 有序性 |
|---|---|---|---|
| 是否保证 | 不保证,原因请看下面模拟的代码 | 保证 | 保证 |
| 底层原理 | 编译器优化指令重排,volatile不能保证 | 修改汇编Lock前缀指令,监听MESI缓存一致性协议 | 读写的内存屏障指令 |
模拟原子性:volatile不保证可见性,使用AtomicInteger就可以保证原子性
// 不保证原子性:使用javap -c反编译查看原因
private static void NotAutomaticByVolatile() {
// 1.资源类
MyResources myResources = new MyResources();
// 2.模拟volatile不保证原子性
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myResources.add1();
}
}, String.valueOf(i)).start();
}
// 3.默认的2个线程是main和GC,所以让大于这个2个线程的等待
while (Thread.activeCount() > 2) {
Thread.yield();
}
// 4.volatile修饰的共享变量,不能保证原子性
System.out.println(Thread.currentThread().getName() + ",data:" + myResources.data);
}
public class MyResources {
volatile int data = 0;
void add() {
this.data = 1;
}
void add1() {
this.data++;
}
} 
模拟可见性:volatile是一个轻量级的同步锁,保证可见性
private static void seeOkByVolatile() {
MyResources myResources = new MyResources();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ",进入啦");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myResources.add();
System.out.println(Thread.currentThread().getName() + ",已经修改data:" + myResources.data);
}, "线程A").start();
while (myResources.data == 0) {
/* System.out.println(Thread.currentThread().getName() + ",while等待");*/
}
System.out.println(Thread.currentThread().getName() + ",模拟结束,data:" + myResources.data);
}
public class MyResources {
volatile int data = 0;
void add() {
this.data = 1;
}
} volatile实现可见性的底层原理:

模拟有序性:单例设计模式
public class SingletonDemo {
private static volatile SingletonDemo instance;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + " 线程,创建实例了");
}
// 单线程下的单例设计模式是不安全的,会重复new 实例
public static SingletonDemo getInstance() {
if (instance == null) {
instance = new SingletonDemo();
}
return instance;
}
// 共享变量未加volatile修饰的双端锁:由于指令重排序,还是有线程不安全的问题
public static SingletonDemo getInstance1() {
if (instance == null) {
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance1();
}, String.valueOf(i)).start();
}
}
} 执行结果:无论多少次,就只能有一个线程创建实例
0 线程,创建实例了
volatile实现有效性底层原理:读写的内存屏障指令

原子性和CAS
AtomicInteger和CAS
| AtomicInteger | 解释 |
|---|---|
| 概念 | AtomicInteger 内部使用 CAS 原子语义来处理加减等操作 |
| CAS底层原理 | 自旋锁+Unsafe类(原子操作) |
| 缺点 | 循环时间长,开销大;只能保证单个共享变量的原子操作;存在ABA问题 |

// 不保证原子性:使用javap -c反编译查看原因
private static void atomicByAtomicInteger() {
// 1.资源类
MyResources myResources = new MyResources();
// 2.atomicInteger保证原子性
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myResources.add1();// i++等操作是不保证原子性
myResources.addByAtomic();// 使用automicInteger保证原子性
}
}, String.valueOf(i)).start();
}
// 3.默认的2个线程是main和GC,所以让大于这个2个线程的等待
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + ",data:" + myResources.data);
System.out.println(Thread.currentThread().getName() + ",atomicInteger:" + myResources.atomicInteger);
}
public class MyResources {
volatile int data = 0;
AtomicInteger atomicInteger = new AtomicInteger();
void add() {
this.data = 1;
}
void add1() {
this.data++;
}
void addByAtomic() {
this.atomicInteger.getAndIncrement();
}
} ABA问题
CAS会出现ABA问题:自旋操作只关心A和A的结果,中间B可能出现很多次,于是线程不安全
public class ABAQuestion {
public static void main(String[] args) {
AtomicReference<Integer> reference = new AtomicReference<>(1);
new Thread(() -> {
reference.compareAndSet(1, 2);
reference.compareAndSet(2, 1);
}, "线程1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
reference.compareAndSet(1, 1000);
System.out.println(Thread.currentThread().getName() + ",update:" + reference.get());
}, "线程2").start();
}
} 解决ABA问题
解决ABA问题:使用AtomicStampedReference每次CAS指定版本号/时间戳
public class ABASolution {
public static void main(String[] args) {
AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);
new Thread(() -> {
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 版本号1:" + stamp);
//暂停1s等t2线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
stampedReference.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + " 版本号2:" + stampedReference.getStamp());
stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + " 版本号3:" + stampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + " 当前实际值:" + stampedReference.getReference());
}, "t1").start();
new Thread(() -> {
int exceptStamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 期望版本号:" + exceptStamp);
//暂停3s等t1线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// t1 线程执行完毕后,版本号 = 3 与 这里的 exceptStamp永不相同,所以执行会失败
boolean result = stampedReference.compareAndSet(100, 200, exceptStamp, exceptStamp + 1);
int nowStamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 修改值结果:" + result + " 当前版本号:" + nowStamp + " 期望版本号:" + exceptStamp);
System.out.println(Thread.currentThread().getName() + " 当前实际值:" + stampedReference.getReference());
}, "t2").start();
}
} 执行结果:
t1 版本号1:1 t2 期望版本号:1 t1 版本号2:2 t1 版本号3:3 t1 当前实际值:100 t2 修改值结果:false 当前版本号:3 期望版本号:1 t2 当前实际值:100
单例设计模式
6种常见的单例模式写法
public class Singleton1 {
private static Singleton1 instance;
private Singleton1() {
}
// 方式1:懒汉式,线程不安全
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
} public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {
}
// 方式2:懒汉式,线程安全,加了synchronized
public static synchronized Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
} public class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3() {
}
// 方式2:饿汉式,线程安全,因为classloader机制避免了多线程的同步问题
public static synchronized Singleton3 getInstance() {
return instance;
}
}
public class Singleton4 {
// volatile防止代码指令重排
private volatile static Singleton4 instance;
private Singleton4() {
}
// 方式4:双端锁+volatile
public static Singleton4 getInstance() {
// 双端锁提高效率
if (instance == null) {
synchronized (Singleton4.class) {
if (instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
} public class Singleton5 {
private Singleton5() {
}
private static class SingletonHandle {
private static final Singleton5 INSTANCE = new Singleton5();
}
// 方式5:静态内部类,比方式4更合理,因为内部类被装载了,才会使用;并且类加载机制保障了线程安全
public static Singleton5 getInstance() {
return SingletonHandle.INSTANCE;
}
} public enum Singleton6 {
INSTANCE;
// 方式6:枚举,更简洁,自动支持序列化机制,绝对防止多次实例化。
public void whateverMethod() {
}
} 集合不安全
并发修改异常:ConcurrentModificationException
// 普通的集合类都是线程不安全的:java.util.ConcurrentModificationException
private static void ListIsNotSafe() {
List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list.toString());
}, String.valueOf(i)).start();
}
} 集合安全的方法
集合安全:
1.老API:Vector
2.集合安全工具类:Collections.synchronizedList
3.读写锁:new CopyOnWriteArrayList<>() private static void ListSafe() {
List<String> list = new ArrayList<>();
List<String> list1 = new Vector<>();
List<String> list2 = Collections.synchronizedList(list);
List<String> list3 = new CopyOnWriteArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list.toString());
}, String.valueOf(i)).start();
}
} CopyOnWrite
底层原理:读写分离,写时复制
// CopyOnWriteArrayList的add源码
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
} HashSet底层
底层就是HashMap,但是add(e),是将key=e,value= PRESENT(map中的对象关联的虚拟值)
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
...
public boolean add(E e) {
return map.put(e, PRESENT)==null;
} 重入锁
Syn是可重入锁
class Phone {
public synchronized void method1() {
System.out.println(Thread.currentThread().getName() + ",进入method1");
method2();
}
private synchronized void method2() {
System.out.println(Thread.currentThread().getName() + ",进入method2");
}
}
public class SynIsRepeatableLock {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.method1();
}, "线程1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.method1();
}, "线程2").start();
}
} ReentrantLock是重入锁:(补充)加锁几次和解锁几次必须配对,如果配对都会生效
class Resources implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
method1();
}
// 补充:加锁几次和解锁几次必须配对,如果配对都会生效
private void method1() {
lock.lock();
lock.lock();
System.out.println(Thread.currentThread().getName() + ",进入method1");
method2();
lock.unlock();
lock.unlock();
}
private void method2() {
lock.lock();
System.out.println(Thread.currentThread().getName() + ",进入method2");
lock.unlock();
}
}
public class ReenIsRepeatableLock {
public static void main(String[] args) {
Resources resources = new Resources();
Thread t1 = new Thread(resources);
Thread t2 = new Thread(resources);
t1.start();
t2.start();
}
} 自旋锁
| 自旋锁 | 解释 |
|---|---|
| 概念 | 正在获取锁的现在不会立刻阻塞,会循环等待其他线程释放锁的资源 |
| 优点 | 减少上下文线程切换的消耗 |
| 缺点 | 循环等待,消耗的是CPU资源 |
手写一个自旋锁:
class MyResource {
}
public class WhileLockDemo {
AtomicReference<MyResource> atomicReference = new AtomicReference<>();
MyResource myResource = new MyResource();
private void method1() {
System.out.println(Thread.currentThread().getName() + ",加锁啦");
while (!atomicReference.compareAndSet(null, myResource)) {
}
}
private void method2() {
atomicReference.compareAndSet(myResource, null);
System.out.println(Thread.currentThread().getName() + ",解锁啦");
}
public static void main(String[] args) {
WhileLockDemo demo = new WhileLockDemo();
new Thread(() -> {
demo.method1();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.method2();
}, "线程1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
demo.method1();
demo.method2();
}, "线程2").start();
}
} 读写锁
class MyCache {
//缓存应用的资源,必须使用volatile修饰
private volatile Map<String, Object> map = new HashMap<>();
//加入读写锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 模拟写锁上锁解锁
*/
public void put(String key, Object value) {
//写锁上锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在写入,不可共享");
map.put(key, value);
//模拟网络拥堵
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
//写锁解锁
readWriteLock.writeLock().unlock();
}
}
/**
* 模拟读锁上锁解锁
*/
public void get(String key) {
//读锁上锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在读取,可共享");
Object result = map.get(key);
//模拟网络拥堵
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
//读锁解锁
readWriteLock.readLock().unlock();
}
}
} public class ReadWriteLockDemo {
public static void main(String[] args) {
//模拟读写锁:主要是学习map用volatile修饰
MyCache cache = new MyCache();
//写锁:独占
for (int i = 1; i <= 5; i++) {
final int tempInt = 1;
new Thread(() -> {
cache.put(tempInt + "", tempInt + "");
}, String.valueOf(i)).start();
}
//读锁:可共享
for (int i = 1; i <= 5; i++) {
final int tempInt = 1;
new Thread(() -> {
cache.get(tempInt + "");
}, String.valueOf(i)).start();
}
}
} 执行结果:
2 正在写入,不可共享 2 写入完成 1 正在写入,不可共享 1 写入完成 3 正在写入,不可共享 3 写入完成 4 正在写入,不可共享 4 写入完成 5 正在写入,不可共享 5 写入完成 1 正在读取,可共享 2 正在读取,可共享 3 正在读取,可共享 4 正在读取,可共享 5 正在读取,可共享 3 读取完成:1 2 读取完成:1 1 读取完成:1 5 读取完成:1 4 读取完成:1
Reentrant、Semaphore、CountDownLatch、CyclicBarrier区别
| Reentrant | Semaphore | CountDownLatch | CyclicBarrier |
|---|---|---|---|
| 一次只允许一个线程访问某个资源 | 可以指定多个线程同时访问某个资源 | 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行 | 多个线程互相等待,直到达到同一个同步点(屏障),再继续一起执行。 |
Semaphore
概念:一种基于技术的信号量,调用方法是acquire()、release()
public class SemaphoreDemo {
public static void main(String[] args) {
// 模拟3个车位,同时只能有3个线程同时访问,形参=1就是syn
// 1.同步器semaphore(信号量):指定X个线程可以获取资源
Semaphore semaphore = new Semaphore(3);
// 2.创建4个线程=有4个车位
for (int i = 1; i <= 4; i++) {
new Thread(() -> {
try {
// 3.信号量开始计算
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "\t 停1s后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 4.信号量释放计数
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
} 执行结果:
1 抢到车位 3 抢到车位 2 抢到车位 2 停1s后离开车位 1 停1s后离开车位 3 停1s后离开车位 4 抢到车位 4 停1s后离开车位
CountDownLatch
概念:单线程等待,调用方法是await()
- 使用枚举类给线程命名,学习countDownLatch和枚举类减少if判断作用
public enum CountryEnum {
ONE(1, "齐"),
TWE(2, "燕"),
THREE(3, "楚"),
FOUR(4, "赵"),
FIVE(5, "魏"),
SIX(6, "韩");
private Integer retCode;
private String retMessage;
//Enum自带Setter,只用生产getter
public Integer getRetCode() {
return retCode;
}
public String getRetMessage() {
return retMessage;
}
//构造器
CountryEnum(Integer retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}
//获取枚举类中的值
public static CountryEnum forEach_CountryEnum(Integer codeIndex) {
// 枚举自带的元素数组,可用于遍历
CountryEnum[] eles = CountryEnum.values();
for (CountryEnum ele : eles) {
if (codeIndex == ele.getRetCode()) {
return ele;
}
}
return null;
}
} public class CountDownLatchDemo {
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "国,被灭了");
countDownLatch.countDown();
// 使用枚举类给线程命名,学习countDownLatch和枚举类减少if判断作用
}, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + ":秦国一统天下");
}
} 执行结果:
齐国,被灭了 魏国,被灭了 赵国,被灭了 楚国,被灭了 燕国,被灭了 韩国,被灭了 main:秦国一统天下
CyclicBarrier
概念:循环屏障,多线程等待,调用方法是await()
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 同步器CyclicBarrier:指定屏障前等待线程数量, 到达屏障后执行的语句
CyclicBarrier barrier = new CyclicBarrier(7, () -> {
// 构造器第二个参数:Runnable接口
System.out.println("龙珠齐,召唤神龙");
});
for (int i = 1; i <= 7; i++) {
final int resources = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 收集到第" + resources + "颗龙珠");
//满足屏障初始化条件才能执行,否则等待
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
} 执行结果:
1 收集到第1颗龙珠 3 收集到第3颗龙珠 4 收集到第4颗龙珠 2 收集到第2颗龙珠 5 收集到第5颗龙珠 6 收集到第6颗龙珠 7 收集到第7颗龙珠 龙珠齐,召唤神龙
AQS
概念:是1个抽象的队列同步器,通过维护 1个共资源状态( Volatile Int State )和一个先进先出( FIFO )的线程等待队列实现同步
底层原理:维护了一个共享资源的变量state


多线程
创建线程的方式
- extends Thread
- implements Runnable
- implements Callable<类型>
- 使用线程池创建
public class ThreadDemo1 extends Thread {
// 1.继承Thread
// 2.重写run
@Override
public void run() {
System.out.println("threadDemo1 extends thread");
}
public static void main(String[] args) {
// 3.调用start方法
new ThreadDemo1().start();
}
} public class ThreadDemo2 implements Runnable {
// 1.implements Runnable
// 2.重写run
@Override
public void run() {
System.out.println("ThreadDemo2 implements Runnable");
}
public static void main(String[] args) {
// 3.调用run方法
new ThreadDemo2().run();
}
} public class ThreadDemo3 implements Callable<String> {
// 1.3 implements Callable<T>
// 2.重写call方法
@Override
public String call() throws Exception {
return "ThreadDemo3 implements Callable<String>";
}
public static void main(String[] args) {
try {
// 3.call()方法执行
System.out.println(new ThreadDemo3().call());
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ThreadDemo3 implements Callable<String> {
// 1.3 implements Callable<T>
// 2.重写call方法
@Override
public String call() throws Exception {
return "ThreadDemo3 implements Callable<String>";
}
public static void main(String[] args) {
try {
// 3.call()方法执行
System.out.println(new ThreadDemo3().call());
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ThreadDemo4 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, 2,
1L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5));
pool.execute(() -> {
System.out.println("执行业务逻辑");
});
pool.shutdown();
}
} Callable和Runnable的区别
| 区别 | Callable | Runnable |
|---|---|---|
| 返回值 | Callable有返回值,调用Callable接口的线程结束后该返回值 | 无返回值 |
| 重写方法 | call() | run() |
| 调用形式 | Callable 在Thread中没有构造方法支持,所以使用FutureTask作为中间人传入,再作为参数传入Thread | Thread中传入参数 |
class CallableThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("多个线程抢一个futureTask,只有有一个进入。Come in Callable");
TimeUnit.SECONDS.sleep(2);
return 100;
}
}
class RunnableThread implements Runnable {
@Override
public void run() {
System.out.println("Runnable,没有返回值");
}
} public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.Callable利用中间人FutureTask先存放资源类
FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableThread());
Thread thread1 = new Thread(futureTask, "线程一");
Thread thread2 = new Thread(futureTask, "线程二");
// 2.多个线程强一个futureTask,只运行一个Callable
thread1.start();
thread2.start();
// 3.main线程模拟器其他线程的运算结果
Integer otherRes = 100;
// 4.FutureTask使用方法:使得CallableThread执行完再走,防止其余线程阻塞影响效率
while (!futureTask.isDone()) {
}
// 5.获取CallableThread执行结果
Integer res = futureTask.get();
System.out.println(Thread.currentThread().getName() + "\t 所有线程的计算结果:" + (otherRes + res));
}
} 执行结果:
多个线程抢一个futureTask,只有有一个进入。Come in Callable main 所有线程的计算结果:200
线程状态
| 线程状态 | 描述 |
|---|---|
| new | 新建。属于一个已经创建的线程,但是还没有调用start方法启动的线程所处的状态。 |
| runnable | 就绪。有可能正在运行,或者正在等待CPU资源。总体上就是当我们创建线程并且启动之后,就属于Runnable状态。 |
| blocked | 阻塞。当线程准备进入synchronized同步块或同步方法的时候,申请一个监视器锁而进行的等待,会使线程进入BLOCKED状态 |
| waiting/timed_waiting | 等待。调用了Object.wait()或者Thread.join()或者LockSupport.park()。处于该状态下的线程在等待另一个线程 执行一些其余action来将其唤醒。 |
| running | 运行。处于运行状态的线程 主要 run 方法 中的逻辑代码 |
| terminated | 消亡。比较容易理解,那就是线程执行结束了,run方法执行结束表示线程处于消亡状态了。 |

线程的基本方法
6种常见方法
| 方法名 | 描述 |
|---|---|
| sleep | 线程休眠,是Thread类的静态方法,不会释放锁,使得线程进入TIMED-WAITING状态 |
| wait | 线程等待,是Object类的非静态方***释放占有的锁,使得线程进入WAITING状态,所以通常用于同步代码块 |
| interrupt | 线程中断,影响该线程内部的一个中断标识位,但并不会因为调用了 interrupt 方法而改变线程状态 |
| yield | 线程让步,使当前线程让出(释放) CPU 时间片, 与其他线程重新竞争 |
| join | 线程加入,等待其他线程终止,当前线程调用子或另一个线程join方法,当前线程阻塞等待join线程执行完毕 |
| notify | 线程唤醒,是Object类的非静态方法,唤醒等待的一个线程,如果全部线程都在等待,则随机唤醒 |

sleep和wait区别
| 区别 | sleep | wait |
|---|---|---|
| 父亲不同 | Thread类 | Object类 |
| 含义不同 | 必须指定等待时间,结束后,恢复运行状态 | 可以不指定等待时间 |
| 是否释放锁不同 | 不会释放对象锁 | 会释放锁,notify唤醒后,才会重新获取对象锁 |
start和run区别
| 区别 | start | run |
|---|---|---|
| 含义不同 | 启动线程,无需等待run方法执行完毕就可继续执行下面代码 | 指定运行线程的run方法,执行完run方法后,线程终止 |
| 状态不同 | thread.start()线程处于就绪状态,没有运行 | thread.run()线程处于运行状态 |
终止线程方式
正常运行结束
使用退出标志位退出线程
public class StopThread1 extends Thread { // 1.volatile修饰的标志位 private volatile boolean exit = false; @Override public void run() { // 2.while判断是否跳出 while (!exit) { // 执行业务逻辑代码 System.out.println("执行业务逻辑,使得exit=true"); } } }使用interrpter终止线程
- 在调用sleep等方法后,抛出异常后,配合break跳出循环,才能结束run方法
public class StopThread2 extends Thread { @Override public void run() { // 1.未阻塞判断中断标志位跳出 while (!isInterrupted()) { try { // 2.线程处于阻塞状态,调用interrupter方法后会抛出异常 Thread.sleep(5 * 1000); } catch (InterruptedException e) { e.printStackTrace(); // 3.抛出异常后,break才跳出循环,才能结束run方法 break; } } } }使用stop方法终止线程
- 直接调用Thread.stop()是很危险的,极度不安全
线程死锁
两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
死锁条件
| 死锁条件 | 概述 | 破坏死锁 |
|---|---|---|
| 互斥条件 | 该资源任意一个时刻只由一个线程占用 | 无法破坏,因为使用锁的本意就是想让它们互斥的(临界资源需要互斥访问) |
| 请求和保持 | 一个线程 / 进程因请求资源而阻塞时,对已获得的资源保持不放 | 一次性申请所有的资源 |
| 不剥夺 | 线程 / 进程已获得的资源在末使用完之前不能被其他线程 / 进程强行剥夺,只有自己使用完毕后才释放资源 | 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源 |
| 循环等待 | 若干线程 / 进程之间形成一种头尾相接的循环等待资源关系 | 按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件(最常用) |

手写死锁
写法1:
public class DeadLockDemo1 {
// 资源1,让线程1获得
private static Object resource1 = new Object();
// 资源2,让线程2获得
private static Object resource2 = new Object();
public static void main(String[] args) {
// 线程1
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + ":get resource1");
// 线程1等待1s,让线程2获得资源2
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + ":get resource2");
}
}
}, "线程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + ":get resource2");
// 线程2等待1s,让线程1获得资源1
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + ":get resource1");
}
}
}, "线程 2").start();
}
} 执行结果:
线程 1:get resource1 线程 2:get resource2 线程 1:waiting get resource2 线程 2:waiting get resource1
如何避免上述的死锁:让线程2获得资源的锁的顺序和线程1一样,因为破坏了相互等待的条件,所以避免了死锁
写法2:
public class DeadThread implements Runnable{
String lockA;
String lockB;
public DeadThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockA + ",想持有" + lockB);
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockB + ",想持有" + lockA);
}
}
}
} public class DeadLockDemo2 {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new DeadThread(lockA, lockB), "A").start();
//lockA和lockB互换
new Thread(new DeadThread(lockB, lockA), "B").start();
}
} A 自己持有lockA,想持有lockB B 自己持有lockB,想持有lockA ...
排查死锁
| windows系统 | 解释 |
|---|---|
| jps -l | 输出当前包下进程包,对应的Java程序完全的包名,应用主类名下 |
| jstack 死锁进程名 | 查看指定进程名的在JVM栈中的状态 |


阻塞队列
| 阻塞队列 | 解释 |
|---|---|
| 概念 | 队列空,线程再去拿就阻塞;队列满,线程再添加就阻塞 |
| 好处 | 出现阻塞队列后,程序员不用手动去阻塞队列和唤醒队列 |
| 阻塞队列分类 | 解释 |
|---|---|
| ArrayBlockingQueue | 数组结构组成的有界阻塞队列 |
| LinkedBlockingQueue | 由链表组成的有界阻塞队列(大小默认是21亿,不推荐默认使用) |
| LinkedBlockingDeque | 由链表结构组成的双向阻塞队列 |
| PriorityBlockingQueue | 支持优先级排序的无界阻塞队列 |
| DelayBlockingQueue | 使用优先级队列实现的延迟无界队列 |
| SynchronousQueue | 不存储元素的阻塞队列=单个元素的队列 |
| LinkedTransferQueue | 由链表结构组成的无界阻塞队列 |
| 核心API | 抛出异常 | 返回布尔 | 阻塞 | 超时 |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
| 移除 | remove() | poll() | take() | poll(time,unit) |
| 检查 | element() | peek() | 不可用 | 不可用 |
ArrayBlockingQueue
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
// 抛出异常组:add/remove/element:会抛出异常:IllegalStateException,一言不合就报异常不推荐使用
blockingQueue.add("1");
blockingQueue.add("2");
blockingQueue.add("3");
// blockingQueue.add("4"); 直接抛出异常
blockingQueue.remove("1");
blockingQueue.remove("2");
blockingQueue.remove("3");
System.out.println("--抛出异常组--");
// 返回布尔型组:offer/poll/peek:失败返回布尔型
blockingQueue.offer("11");
blockingQueue.offer("12");
blockingQueue.offer("13");
System.out.println(blockingQueue.offer("14"));
blockingQueue.poll();
blockingQueue.poll();
blockingQueue.poll();
System.out.println("--返回布尔组--");
// 等待组:put/take:满了就一直等待,等待是为了只要有数据出去立马添加
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d"); 这样会一直等待
// System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println("--等待组--");
// 设置时间组:offer/poll设置失败等待时间
System.out.println(blockingQueue.offer("a", 1L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b", 1L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("c", 1L, TimeUnit.SECONDS));
System.out.println("--设置时间组--");
}
} SynchronousQueue
public class SynchronsBlockingQueue {
public static void main(String[] args) {
// SynchronousQueue:一个线程生产一个,等待别的线程消费完才能进行下去
SynchronousQueue<Integer> blockingQueue = new SynchronousQueue();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + ",生产了1");
blockingQueue.put(1);
TimeUnit.SECONDS.sleep(1);
//阻塞:SynchronousQueue使用put必须等待别的线程take后
System.out.println(Thread.currentThread().getName() + ",生产了2");
blockingQueue.put(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程1").start();
new Thread(() -> {
try {
//消费::SynchronousQueue使用put必须等待别的线程take消费
System.out.println(Thread.currentThread().getName() + ",消费了" + blockingQueue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + ",消费了" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程2").start();
}
} 执行结果:
线程1,生产了1 线程2,消费了1 线程1,生产了2 线程2,消费了2
Synchronized和ReenTrantLock区别
相同点:
- 都是可重入锁,默认都是非公平锁
- 都保证了可见性和互斥性
| 不同点 | syn | lock |
|---|---|---|
| 含义 | 关键字,隐式获取锁和释放锁 | Api层面,显示获锁和释放锁 |
| 使用 | 不需要手动释放 | 需要手动释放,否则死锁 |
| 中断 | 不可中断,除非抛出异常或者正常运行完毕 | 可响应式中断,try/trylock(time)/lockInterruptibly |
| 公平 | 只能是非公平锁 | 默认非公平,传参为true表示公平 |
| 底层 | 同步阻塞,悲观策略 | 同步非阻塞,乐观并发策略 |
练习题:(Lock能精确唤醒) AA打印5次,BB打印10次,CC打印15次,紧接着AA打印5次,...重复10论
class MyRenLockResources {
// 1=AA,2=BB,3=CC
private int threadName = 1;
private Lock lock = new ReentrantLock();
private Condition a = lock.newCondition();
private Condition b = lock.newCondition();
private Condition c = lock.newCondition();
public void print5() {
lock.lock();
try {
//1 判断 防止虚假唤醒,使用while
while (threadName != 1) {
//不等于1 就不是AA干活,AA等待
a.await();
}
//2 干活 模拟打印5次
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//3 通知别的线程
threadName = 2;
b.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
lock.lock();
try {
//1 判断 防止虚假唤醒,使用while
while (threadName != 2) {
//不等于1 就不是AA干活,AA等待
b.await();
}
//2 干活 模拟打印5次
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//3 通知别的线程
threadName = 3;
c.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() {
lock.lock();
try {
//1 判断 防止虚假唤醒,使用while
while (threadName != 3) {
//不等于1 就不是AA干活,AA等待
c.await();
}
//2 干活 模拟打印5次
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//3 通知别的线程
threadName = 1;
a.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
} public class SyncAndReentrantLockDemo {
public static void main(String[] args) {
MyRenLockResources myResources = new MyRenLockResources();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
myResources.print5();
}
}, "AA").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
myResources.print10();
}
}, "BB").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
myResources.print15();
}
}, "CC").start();
}
} 线程池参数
使用ThreadPoolExecutor自建线程池(推荐使用)/Executors工具类(不推荐使用)
// 线程池7大参数
public ThreadPoolExecutor(
// 核心线程数
int corePoolSize,
// 最大线程数
int maximumPoolSize,
// 空闲线程存活时间
long keepAliveTime,
// 线程存活时间单位
TimeUnit unit,
// 阻塞队列
BlockingQueue<Runnable> workQueue,
// 线程工厂
ThreadFactory threadFactory,
// 饱和拒绝策略
RejectedExecutionHandler handler
) | 线程池参数 | 解释 |
|---|---|
| corePoolSize | 线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。 |
| maximumPoolSize | 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize; |
| keepAliveTime | 线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime; |
| unit | keepAliveTime的单位 |
| workQueue | 用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列 |
| threadFactory | 它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。 |
| handler | 线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略 |
| workQueue阻塞队列 | 解释 |
|---|---|
| ArrayBlockingQueue | 基于数组结构的有界阻塞队列,按FIFO排序任务 |
| LinkedBlockingQuene | 基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene |
| SynchronousQuene | 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene |
| PriorityBlockingQuene | 具有优先级的无界阻塞队列 |
| handler拒绝策略 | 解释 |
|---|---|
| AbortPolicy | 直接抛出异常,默认策略 |
| CallerRunsPolicy | 用调用者所在的线程(如以下的main线程)来执行任务 |
| DiscardOldestPolicy | 丢弃阻塞队列中靠最前的任务,并执行当前任务 |
| DiscardPolicy | 直接丢弃任务 |
线程池核心组件

创建线程池
方式一:Executors
ExecutorService,其中定义了线程池的具体行为,常见的四种方法如上图红线处
public class ExecutorsDemo {
public static void main(String[] args) {
// 创建线程池方式1:Executors,里面封装了很多,但都不推荐使用
// newFixedThreadPool:核心和最大线程数相同,等待队列是LinkedBlockingQueue,等待时间0
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// newCachedThreadPool:核心为0,等待队列是SynchronousQueue,最大线程数是最大整数,等待时间60s
ExecutorService cachedPool = Executors.newCachedThreadPool();
// newSingleThreadExecutor:核心和最大为0,等待队列是LinkedBlockingQueue,等待时间0
ExecutorService singletonPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
fixedPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ",办理业务");
});
}
fixedPool.shutdown();
}
} 5种常用的线程池,但是阿里巴巴开发手册都不推荐使用


方式二:new ThreadPoolExecutor
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1 推荐使用使用自建的线程池,学习7大参数
ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
2, 5, 1L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
// AbortPolicy:直接抛出异常
// CallerRunsPolicy:用调用者所在的线程(本类中的main)来执行任务
// DiscardPolicy:丢弃进来的任务
// DiscardOldestPolicy:丢弃之前的第一个任务
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 1; i <= 20; i++) {
int resource = i;
// 2.执行线程池中的线程
myThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t 线程进入,\t 获得资源: " + resource);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 3.终止线程池
myThreadPool.shutdown();
// 4.while判断线程池是否终止
while (!myThreadPool.isTerminated()) {
}
System.out.println("所有线程已经终止");
}
} pool-1-thread-1 线程进入, 获得资源: 1 pool-1-thread-4 线程进入, 获得资源: 9 pool-1-thread-3 线程进入, 获得资源: 8 main 线程进入, 获得资源: 11 pool-1-thread-2 线程进入, 获得资源: 2 pool-1-thread-5 线程进入, 获得资源: 10 main 线程进入, 获得资源: 12 pool-1-thread-2 线程进入, 获得资源: 3 pool-1-thread-3 线程进入, 获得资源: 4 pool-1-thread-1 线程进入, 获得资源: 5 pool-1-thread-4 线程进入, 获得资源: 7 pool-1-thread-5 线程进入, 获得资源: 6 main 线程进入, 获得资源: 18 pool-1-thread-2 线程进入, 获得资源: 13 pool-1-thread-4 线程进入, 获得资源: 14 pool-1-thread-1 线程进入, 获得资源: 16 pool-1-thread-3 线程进入, 获得资源: 15 pool-1-thread-5 线程进入, 获得资源: 17 pool-1-thread-2 线程进入, 获得资源: 19 pool-1-thread-1 线程进入, 获得资源: 20 所有线程已经终止
线程池执行原理
- 创建一个线程池,在还没有任务提交的时候,默认线程池里面是没有线程的。当然,你也可以调用prestartCoreThread方法,来预先创建一个核心线程。
- 线程池里还没有线程或者线程池里存活的线程数小于核心线程数corePoolSize时,这时对于一个新提交的任务,线程池会创建一个线程去处理提交的任务。当线程池里面存活的线程数小于等于核心线程数corePoolSize时,线程池里面的线程会一直存活着,就算空闲时间超过了keepAliveTime,线程也不会被销毁,而是一直阻塞在那里一直等待任务队列的任务来执行。
- 当线程池里面存活的线程数已经等于corePoolSize了,这是对于一个新提交的任务,会被放进任务队列workQueue排队等待执行。而之前创建的线程并不会被销毁,而是不断的去拿阻塞队列里面的任务,当任务队列为空时,线程会阻塞,直到有任务被放进任务队列,线程拿到任务后继续执行,执行完了过后会继续去拿任务。这也是为什么线程池队列要是用阻塞队列。
- 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列也满了,这里假设maximumPoolSize>corePoolSize(如果等于的话,就直接拒绝了),这时如果再来新的任务,线程池就会继续创建新的线程来处理新的任务,直到线程数达到maximumPoolSize,就不会再创建了。这些新创建的线程执行完了当前任务过后,在任务队列里面还有任务的时候也不会销毁,而是去任务队列拿任务出来执行。在当前线程数大于corePoolSize过后,线程执行完当前任务,会有一个判断当前线程是否需要销毁的逻辑:如果能从任务队列中拿到任务,那么继续执行,如果拿任务时阻塞(说明队列中没有任务),那超过keepAliveTime时间就直接返回null并且销毁当前线程,直到线程池里面的线程数等于corePoolSize之后才不会进行线程销毁。
- 如果当前的线程数达到了maximumPoolSize,并且任务队列也满了,这种情况下还有新的任务过来,那就直接采用拒绝的处理器进行处理。默认的处理器逻辑是抛出一个RejectedExecutionException异常。你也就可以指定其他的处理器,或者自定义一个拒绝处理器来实现拒绝逻辑的处理(比如讲这些任务存储起来)。JDK提供了四种拒绝策略处理类:AbortPolicy(抛出一个异常,默认的),DiscardPolicy(直接丢弃任务),DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池),CallerRunsPolicy(交给线程池调用所在的线程进行处理)。

ThreadLocal
| ThreadLocal | 解释 |
|---|---|
| 概念 | 提供每个线程存储自身专属的局部变量值 |
| 实现原理 | 调用 ThreadLocal 的 set () 方法时,实际上就是往 ThreadLocalMap 设置值,key 是 ThreadLocal 对象,值是传递进来的对象;调用 ThreadLocal 的 get () 方法时,实际上就是往 ThreadLocalMap 获取值,key 是 ThreadLocal 对象 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。因为这个原理,所以 ThreadLocal 能够实现 “数据隔离”,获取当前线程的局部变量值,不受其他线程影响。 |
| 风险 | ThreadLocal 被 ThreadLocalMap 中的 entry 的 key 弱引用,如果 ThreadLocal 没有被强引用, 那么 GC 时 Entry 的 key 就会被回收,但是对应的 value 却不会回收。就会造成内存泄漏 |
| 解决办法 | 每次使用完 ThreadLocal,都调用它的 remove () 方法,清除数据 |

