学无止境 Java工程师的进阶之旅
目录
一、什么是JUC
1.1、简介
- JUC就是
java.util.concurrent
工具包的简称,这是一个处理线程的工具包,JDK1.5开始出现
1.2、线程和进程
- 进程:计算机分配资源的基本单位,具有独立功能的程序,例如QQ运行起来就是一个进程
- 线程:系统分配处理器时间资源的基本单元,或者说是进程之内独立执行的一个个单元执行流
1.3、线程的状态
- NEW(新建)
- RUNNABLE(准备就绪)
- BLOCKED(阻塞)
- WAITING(等待)
- TIMED_WAITING(超时不等待)
- TERMINATED(终结)
1.4、wait & sleep
- wait:Object的方法,任何对象实例都能调用。如果当前线程占用锁执行wait会释放锁(即代码要在synchronized中)
- sleep:Thread的静态方法,不会释放锁也不需要占用锁
1.5、并发与并行
- 串行:按先后顺序执行
- 并行:多个任务同时执行(边听歌边学习)
- 并发:多个线程访问同一个资源,多个线程对一个点(抢票,秒杀)
1.6、管程
- Monitor监视器(锁):是一种同步机制,保证同一时间只有一个线程访问被保护的数据或代码
JVM同步基于管程对象,进入获取管程对象,退出释放管程对象
1.7、用户线程和守护线程
- 用户线程:自定义线程
- 守护线程:比如垃圾回收线程
主线程结束,用户线程还在运行,JVM存活
主线程结束,没有用户线程,JVM结束
1.8、线程不由java创建
由操作系统本地方法创建
二、Lock
2.1、 复习Synchronized
2.1.1、作用范围
类型 | 作用范围 | 作用对象 |
---|---|---|
代码块 | {} | 调用这个方法的对象 |
方法 | 整个方法 | 调用这个方法的对象 |
静态方法 | 整个静态方法 | 该类的所有对象 |
类 | 括号括起来的部分 | 该类所有对象 |
synchronized不属于方法定义的一部分,因此无法被继承,如果子类重写父类的同步方法,子类并不同步,需要再加synchronized关键字或者子类直接调用父类同步方法
2.1.2、多线程编程步骤
实现高内聚,低耦合
- 创建资源类,创建属性和操作方法
- 创建多线程调用资源类的方法
2.1.3、卖票例子
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
ticket.sale();
}
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
ticket.sale();
}
}
}, "B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
ticket.sale();
}
}
}, "C").start();
}
}
class Ticket {
private int num = 1000;
public synchronized void sale() {
if (num > 0) {
num--;
System.out.println(Thread.currentThread().getName() + "卖出一张票:" + "余票" + num);
}
}
}
2.2、Lock使用与对比
- Lock不是Java语言内置的,synchronized是Java的关键字,因此是内置特性
- Lock是一个类,通过这个类可以实现同步访问
- Lock需要手动释放锁,synchronized代码块执行完后会字段释放锁
2.2.1、Lock接口
ReentrantLock
可重入锁(后文会说,这里直接演示)
import java.util.concurrent.locks.ReentrantLock;
public class LSaleTicket {
public static void main(String[] args) {
LTicket lTicket = new LTicket();
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lTicket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lTicket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lTicket.sale();
}
}, "C").start();
}
}
class LTicket {
private int num = 1000;
// 创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
public void sale() {
lock.lock();
try {
if (num > 0) {
num--;
System.out.println(Thread.currentThread().getName() + "卖出一张票,余票" + num);
}
// 必须放在finally块中解锁,防止死锁
} finally {
lock.unlock();
}
}
}
2.2.2、对比
- Lock是一个接口。synchronized是Java关键字,synchronized是内置的语言实现
- synchronized发生异常时会自动释放线程占有的锁,因此不会导致死锁。
- Lock发生异常时,如果没有主动通过unLock()释放,会导致死锁,因此使用Lock需要在finally块中释放锁
- Lock可以让等待锁的线程响应中断,而synchronized却不行,等待的线程会一直等待下去,不能响应中断
- Lock可以判断有没有成功获取锁,synchronized无法办到
- Lock可以提高多个线程进行读操作的效率(读写锁)
- 当竞争不激烈时两者性能差不多,当竞争非常激烈时,Lock的性能远大于synchronized
三、线程间通信
3.1、概念
在多线程模式下进行工作,除了要考虑各个线程之间是否同步、如何竞争锁等问题,还要考虑这样一个问题:线程之间有的时候需要相互配合来共同完成一件事情。
把一个大的任务拆分成多个不同的任务线,每个任务线中都有更小的执行步骤。各个线程之间需要彼此配合:A 线程执行一步唤醒 B 线程,自己等待;B 线程执行一步,唤醒 A 线程,自己等待……
3.2、核心语法
1、Object 类的 wait() 方法
- wait() 方法会导致当前线程进入等待状态
- 必须是另外一个线程调用 notify() 或 notifyAll() 方法来唤醒
- “for this object” 表示还是要使用同一个对象分别调用 wait()、notify()、notifyAll() 这些方法
2、Object 类的 notify() 方法
- notify() 方法只唤醒一个线程
- 处于等待状态的线程会被存放在对象监视器中的一个数组中
- 如果在这个对象的监视器中维护的处于等待状态的线程是多个,那么 notify() 方法会随机唤醒一个
- notfiy() 方法无法精确唤醒一个指定的线程,这个需求可以通过 Lock + Condition 方式实现(定制化通信)
3、Object 类的 notifyAll() 方法
- 唤醒当前对象监视器上等待的所有线程。
3.3、案例演示
3.3.1、需求分析
- 设定一个成员变量,作为两个线程都要操作的共享数据,设置初始化值为 0
- A 线程执行 +1 操作
- B 线程执行 -1 操作
- A、B 两个线程交替执行
3.3.2、synchronized代码实现
public class STest {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 50; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 50; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
}
}
class Share {
private int num = 0;
public synchronized void incr() throws InterruptedException {
if (num > 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "::" + num);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
if (num <= 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "::" + num);
this.notifyAll();
}
}
3.3.3、虚假唤醒
问题描述:
当上面的例子中,线程数量从两个增加到四个,计算结果就会出错:
thread-a 线程:1
thread-c 线程:2
thread-a 线程:3
thread-b 线程:2
thread-d 线程:1
问题分析:
1、使用 if 的情况(仅判断一次)
假设C线程判断
num<=0
后,进行等待(在哪里等就在哪里醒),被别的线程唤醒后不会再进行num判断
,会导致数值不符合预期
2、使用 while 解决问题
3、小结:
要解决虚假唤醒问题,就需要对线程间通信时的判断条件使用 while 循环结构来执行,而不是 if 分支判断。
3.3.3、Lock代码实现
public class Demo02 {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 50; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 50; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 50; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "CC").start();
}
}
class Share {
private int num = 0;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void incr() throws InterruptedException {
lock.lock();
try {
while (num > 0) {
// 等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "::" + num);
// 通知
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decr() throws InterruptedException {
lock.lock();
try {
while (num <= 0) {
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + "::" + num);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3.4、定制化通信
1、可以通过可重入锁的多种情况来达成,每把钥匙都对应同一把重入锁
private Lock lock = new ReentrantLock();
//声明钥匙 A
private Condition conditionA = lock.newCondition();
//声明钥匙 B
private Condition conditionB = lock.newCondition();
//声明钥匙 C
private Condition conditionC = lock.newCondition();
conditionA.await(); //A等待
conditionA.signal(); //唤醒A
A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮
2、代码演示
public class Laptoy {
public static void main(String[] args) {
Test test = new Test();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
test.printA(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
test.printB(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
test.printC(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}, "CC").start();
}
}
class Test {
//通信对象:0--打印 A 1---打印 B 2----打印 C
private int number = 0;
//声明锁
private Lock lock = new ReentrantLock();
//声明钥匙 A
private Condition conditionA = lock.newCondition();
//声明钥匙 B
private Condition conditionB = lock.newCondition();
//声明钥匙 C
private Condition conditionC = lock.newCondition();
/**
* A 打印 5 次
*/
public void printA(int j) {
try {
lock.lock();
while (number != 0) {
conditionA.await();
}
System.out.println(Thread.currentThread().getName() + "输出 A,第" + j + " 轮开始");
//输出 5 次 A
for (int i = 0; i < 5; i++) {
System.out.println("A");
}
//开始打印 B
number = 1;
//唤醒 B
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* B 打印 10 次
*/
public void printB(int j) {
try {
lock.lock();
while (number != 1) {
conditionB.await();
}
System.out.println(Thread.currentThread().getName() + "输出 B,第" + j + " 轮开始");
//输出 10 次 B
for (int i = 0; i < 10; i++) {
System.out.println("B");
}
//开始打印 C
number = 2;
//唤醒 C
conditionC.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* C 打印 15 次
*/
public void printC(int j) {
try {
lock.lock();
while (number != 2) {
conditionC.await();
}
System.out.println(Thread.currentThread().getName() + "输出 C,第" + j + " 轮开始");
//输出 15 次 C
for (int i = 0; i < 15; i++) {
System.out.println("C");
}
System.out.println("-----------------------------------------");
//开始打印 A
number = 0;
//唤醒 A
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
四、集合的线程安全
4.1、ArrayList集合线程不安全演示
ArrayList 并发情况下会出现ConcurrentModificationException
并发修改异常
public class MySafe {
public static void main(String[] args) {
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);
}, String.valueOf(i)).start();
}
}
}
4.2、解决方案
4.2.1、Vector
1、解决
List<String> list = new Vector<>();
2、源码
// Vector类添加元素方法为同步方法
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
}
3、说明
不会抛异常,线程安全,但是这个类太古老
Vector 是矢量队列,它是 JDK1.0 版本添加的类。继承于 AbstractList,实现了 List, RandomAccess, Cloneable 这些接口。 Vector 继承了 AbstractList,实现了 List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。 Vector 实现了 RandmoAccess 接口,即提供了随机访问功能。RandmoAccess 是 java 中用来被 List 实现,为 List 提供快速访问功能的。在Vector 中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。 Vector 实现了 Cloneable 接口,即实现 clone()函数。它能被克隆
4.2.2、 Collections
1、解决
List<String> list = Collections.synchronizedList(new ArrayList<>());
2、源码
public class Collections {
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
}
3、说明
不会抛异常,但是锁定范围大,性能低
4.2.3、CopyOnWriteArrayList(写时复制)
1、解决
List<String> list = new CopyOnWriteArrayList<>();
2、写时复制
兼顾并发读和独立写:写前先上可重入锁,写数据时复制一份旧数据再写,写完用新数据覆盖旧数据
- 使用写时复制技术要向集合对象中写入数据时:先把整个集合数组复制一份
- 将新数据写入复制得到的新集合数组
- 再让指向集合数组的变量指向新复制的集合数组
3、源码
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
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();
}
}
}
- 优点:兼顾了性能和线程安全,允许同时进行读写操作
- 缺点:由于需要把集合对象整体复制一份,所以对内存的消耗很大
4.3、解决HashSet线程不安全
1、直接使用CopyOnWriteArraySet
代替
public class MySafe {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
2、源码:
所在类:java.util.concurrent.CopyOnWriteArraySet
public boolean add(E e) {
return al.addIfAbsent(e);
}
所在类:java.util.concurrent.CopyOnWriteArrayList
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
4.4、 解决HashMap线程不安全
1、直接使用ConcurrentHashMap
解决
public class MySafe {
public static void main(String[] args) {
// 1、创建集合对象
Map<String, String> map = new ConcurrentHashMap<>();
// 2、创建多个线程执行读写操作
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
String key = UUID.randomUUID().toString().replace("-", "").substring(0, 5);
String value = UUID.randomUUID().toString().replace("-", "").substring(0, 5);
map.put(key, value);
System.out.println("map = " + map);
}
}, "thread" + i).start();
}
}
}
2、说明
ConcurrentHashMap 底层使用的是『锁分段』技术。它的典型应用就是用来实现微服务的注册中心。
- 微服务名称:作为 Map 的 key
- 微服务对象:作为 Map 的 value
- 使用 ConcurrentHashMap 实现并发读写
- 发现服务:读操作
- 注册服务:写操作
五、八锁现象
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的 Class 对象。
- 对于同步方法块,锁是 Synchonized 括号里配置的对象
5.1、对象层
普通 synchronized 锁的对象是方法的调用者
1、两个同步方法,同一个对象演示
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}
}
class Phone {
public synchronized void sendEmail() throws Exception {
System.out.println("---sendEmail---");
}
public synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
}
结果:同一把锁,所以先sendEmail
---sendEmail---
---sendSMS---
2、 两个同步方法,同一个对象,停 4 秒在短信方法内
class Phone {
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(3);
System.out.println("---sendEmail---");
}
public synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
}
结果:同一把锁,依然是先sendEmail
---sendEmail---
---sendSMS---
3、两个对象,两个同步方法(注意睡眠时间)
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone1.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
phone2.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}
}
class Phone {
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("---sendEmail---");
}
public synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
}
结果:不同锁,先sendSMS,不同对象在普通同步方法分配的是不同的锁
---sendSMS---
---sendEmail---
5.2、普通方法层
普通方法没有锁!不是同步方法,就不受锁的影响,正常执行
4、 一个对象,新增普通的 hello 方法
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
new Thread(() -> {
try {
phone.hello();
} catch (Exception e) {
e.printStackTrace();
}
}, "C").start();
}
}
class Phone {
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendEmail---");
}
public synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
public void hello() {
System.out.println("---hello---");
}
}
结果:先hello,不受锁的影响
---hello---
---sendEmail---
---sendSMS---
5.3、类层
不同实例对象的Class类模板只有一个,static静态的同步方法,锁的是Class
5、两个静态的同步方法,一个对象
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}
}
class Phone {
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("---sendEmail---");
}
public static synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
}
结果:同一把锁,先sendEmail
---sendEmail---
---sendSMS---
6、两个对象!增加两个静态的同步方法
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone1.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
phone2.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}
}
class Phone {
public static synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println("---sendEmail---");
}
public static synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
}
结果:同一把锁,锁的是类,依然是先sendEmail
---sendEmail---
---sendSMS---
5.4、混合层
静态同步方法锁的是Class类模板,普通同步方法锁的是实例化的对象,锁的对象不同
7、1个静态的同步方法,1个普通的同步方法 ,一个对象(注意睡眠时间)
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}
}
class Phone {
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendEmail---");
}
public static synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
}
结果:不同锁,先sendSMS
---sendSMS---
---sendEmail---
8、1个静态的同步方法,1个普通的同步方法 ,两个对象(注意睡眠时间)
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone1.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
phone2.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}
}
class Phone {
public synchronized void sendEmail() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendEmail---");
}
public static synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
}
结果:不同锁,先sendSMS
---sendSMS---
---sendEmail---
六、多线程锁
6.1、公平锁与非公平锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
- 优点:所有的线程都能得到资源,不会饿死在队列中。
- 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
-
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必唤醒所有线程,会减少唤起线程的数量。
-
缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
创建可重入锁的时候可以设置公平或非公平锁(默认是非公平锁 )
6.2、可重入锁
- synchronized:隐式,自动获取释放锁
- Lock:显式,手动获取释放锁
6.2.1、synchronized演示可重入锁
1、拿到外层的锁就可以拿到内层的锁
public class SyncLock {
public static void main(String[] args) {
Object o = new Object();
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "外层锁");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "中层锁");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "内层锁");
}
}
}
}, "laptoy-t").start();
}
}
// laptoy-t外层锁
// laptoy-t中层锁
// laptoy-t内层锁
2、递归锁
public class SyncLock {
public static void main(String[] args) {
new SyncLock().add();
}
synchronized void add() {
add();
}
}
// 栈溢出异常,证明可重入锁可以递归调用
6.2.2、Lock演示可重入锁
public class SyncLock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(() -> {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "外层锁");
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "内层锁");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}, "laptoy-t").start();
}
}
// laptoy-t外层锁
// laptoy-t内层锁
6.3、死锁
6.3.1、简介
两个或两个以上进程执行过程中,因为争夺资源而造成的一种互相等待的现象,如果没有外力干涉,他们无法再执行下去
6.3.2、产生原因
- 系统资源不足
- 进程运行推荐顺序不合适
- 资源分配不当
6.3.3、快速演示死锁代码
public class DeadLock {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "线程 持有锁a,试图获取锁b");
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "线程 获取锁b");
}
}
}, "A").start();
new Thread(() -> {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "线程 持有锁b,试图获取锁b");
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "线程 获取锁a");
}
}
}, "B").start();
}
}
// A线程 持有锁a,试图获取锁b
// B线程 持有锁b,试图获取锁b
6.3.4、验证死锁
执行jps
查询正在执行的进程
执行jstack 13484
追踪堆栈
七、Callable接口
7.1、简介
目前我们学习了有两种创建线程的方法-一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程。但是,Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口。
7.2、特点
- 为了实现 Runnable,需要实现不返回任何内容的
run()
方法,而对于Callable,需要实现在完成时返回结果的call()
方法。 call()
方法可以引发异常,而run()
则不能。- 为实现 Callable 而必须重写 call 方法
- 不能直接替换Runnable创建线程,因为 Thread 类的构造方法根本没有 Callable
7.3、FutureTask
7.3.1、源码
继承FutureTask类,该类实现了Runnable接口,构造方法可以传入Callable实现类
public class FutureTask<V> implements RunnableFuture<V> {
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
}
public interface RunnableFuture<V> extends Runnable, Future<V> {}
7.3.2、原理
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成
- 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
- 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
- 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
- 一旦计算完成,就不能再重新开始或取消计算
- get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
- get 只计算一次,因此 get 方法放到最后
7.3.3、使用
1、使用原生方式创建线程
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
new Thread(futureTask, "A").start(); // A线程执行成功
System.out.println(futureTask.get()); // 20
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "线程执行成功");
return 20;
}
}
2、使用Lambda表达式
public class Demo02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行成功");
return 20;
});
new Thread(futureTask, "A").start(); // A线程执行成功
System.out.println(futureTask.get()); // 20
}
}
3、isDone()
方法验证未来任务只需要计算一次
public class Demo03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "执行成功");
return 20;
});
new Thread(futureTask, "主线程").start();
while (!futureTask.isDone()) {
System.out.println("wait...计算中");
}
System.out.println("-----未来任务计算成功-----");
System.out.println("第一次获取未来任务的结果:" + futureTask.get());
System.out.println("第二次获取未来任务的结果:" + futureTask.get());
System.out.println(Thread.currentThread().getName() + "执行结束");
}
}
八、辅助类
8.1、简介
JUC 中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过多时 Lock 锁的频繁操作。这三种辅助类为:
- CountDownLatch: 减少计数
- CyclicBarrier: 循环栅栏
- Semaphore: 信号灯
8.2、减少计数CountDownLatch
CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句
- CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
- 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
- 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行
场景:6个同学离开教室再锁门
public class Demo01 {
public static void main(String[] args) throws Exception {
// 定义一个数值为6的计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
// 创建6个同学
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "号同学离开了教室");
// 计数减1
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
// 计数器未清0前等待
countDownLatch.await();
System.out.println("全部离开了,现在的计数器为" + countDownLatch.getCount());
System.out.println("锁门");
}
}
8.3、循环栅栏 CyclicBarrier
CyclicBarrier 看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作
场景: 集齐 7 颗龙珠就可以召唤神龙
public class Demo01 {
public static void main(String[] args) throws Exception {
CyclicBarrier c = new CyclicBarrier(7, () -> {
System.out.println("集齐七颗龙珠召唤神龙成功");
});
for (int i = 1; i <= 7; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 星龙收集成功");
// 等待达到目标数
c.await();
} catch (Exception e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
看一下源码,构造方法传入的数量赋值给count
每次调用await其实是调用dowait()
每次调用count-1,直到index=0时就放行
8.4、 信号灯 Semaphore
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方法获得许可证,release 方法释放许可
场景:六辆车三个车位
public class Demo01 {
public static void main(String[] args) throws Exception {
// 设置三个车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
// 抢占
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 抢到了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放
semaphore.release();
System.out.println(Thread.currentThread().getName() + " ----离开了车位");
}
}, String.valueOf(i)).start();
}
}
}
九、读写锁
9.1、回顾锁
回顾悲观锁和乐观锁的概念
- 悲观锁:单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低
- 乐观锁:每执行一件事情,都会比较数据版本号,谁先提交,谁先提交版本号
新概念:
- 表锁:整个表操作,不会发生死锁
- 行锁:每个表中的单独一行进行加锁,会发生死锁
- 读锁:共享锁(可以有多个人读),会发生死锁
- 写锁:独占锁(只能有一个人写),会发生死锁
9.2、读写锁
读写锁:一个资源可以被多个读线程访问,也可以被一个写线程访问,但不能同时存在读写线程,读写互斥,读读共享
读写锁ReentrantReadWriteLock
1、 读锁为ReentrantReadWriteLock.ReadLock
,readLock()
方法
2、 写锁为ReentrantReadWriteLock.WriteLock
,writeLock()
方法
创建读写锁对象private ReadWriteLock rwLock = new ReentrantReadWriteLock();
1、写锁 加锁rwLock.writeLock().lock();
2、写锁 解锁rwLock.writeLock().unlock()
;
3、读锁 加锁rwLock.readLock().lock();
4、读锁 解锁rwLock.readLock().unlock()
;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
// 写锁线程
for (int i = 1; i <= 20; i++) {
int num = i;
new Thread(() -> {
myCache.put(num + "", num + "");
}, String.valueOf(i)).start();
}
// 读锁线程
for (int i = 1; i <= 20; i++) {
int num = i;
new Thread(() -> {
myCache.get(num + "");
}, String.valueOf(i)).start();
}
}
}
class MyCache {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private volatile Map<String, Object> map = new HashMap<>();
// 写数据
public void put(String key, Object value) {
// 写锁 加锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在写操作" + key);
// 模拟写操作花时
TimeUnit.MICROSECONDS.sleep(300);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写完了" + key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 写锁 解锁
readWriteLock.writeLock().unlock();
}
}
// 读数据
public Object get(String key) {
Object result = null;
// 读锁 加锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在读操作" + key);
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 读完了" + key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 读锁 解锁
readWriteLock.readLock().unlock();
}
return result;
}
}
9.3、锁的局限性
- 无锁:多线程抢夺资源
- synchronized和ReentrantLock:都是独占,每次只可以一个操作,不能共享
- ReentrantReadWriteLock:读读可以共享,提升性能,但是不能多人写
缺点:造成死锁(一直读,不能写),读进程不能写,写进程可以读。 - 通过(写锁释放前可以读这一特性)进行锁降级,可以提高效率
9.4、写锁的锁降级
1、目的: 写锁降级为读锁(一般等级写锁高于读锁)(利用写锁时可以读这一特性)
2、正常情况是获取写锁,释放写锁,获取读锁,释放读锁
3、降级流程:获取写锁->获取读锁->释放写锁->释放读锁
其实就是在写锁释放前进行获取读锁,可以提高线程效率
public class Demo1 {
public static void main(String[] args) {
//可重入读写锁对象
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁
//锁降级
//1 获取写锁
writeLock.lock();
System.out.println("laptoy");
//2 获取读锁
readLock.lock();
System.out.println("---read---");
//3 释放写锁
writeLock.unlock();
//4 释放读锁
readLock.unlock();
}
}
十、阻塞队列 BlockingQueue
10.1、简介
阻塞队列是共享队列(多线程操作),一端输入,一端输出
所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
执行流程:
- 当队列是空的,从队列中获取元素的操作将会被阻塞
- 当队列是满的,从队列中添加元素的操作将会被阻塞
- 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
- 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
举个例子:
生产者消费者问题:
通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然
- 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列
- 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒
10.2、种类
第一第二比较常用
1、ArrayBlockingQueue(数组有界阻塞队列)
生产者和消费者共用同一个锁,无法并行
2、 LinkedBlockingQueue(链表有界阻塞队列),默认值为integer.MAX_VALUE
生产者和消费者采用了独立的锁,可以并行
3、PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现
Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
4、DelayQueue
类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
5、SynchronousQueue
一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
6、LinkedBlockingDeque
使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
7、LinkedTransferQueue
它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。
注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。
10.3、方法
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
- 抛出异常
当队列满再add()会抛出IllegalStateException:Queue full
当队列空再remove()会抛NoSuchElementException - 特殊值
插入成功true,失败false
移除成功返回出队列的元素,失败返回null - 阻塞
队列满继续put会阻塞直到put成功或响应中断退出
队列空继续take会阻塞直到take成功 - 超时退出
阻塞队列满时,队列阻塞生产者一定时间,超时后生产者线程退出
10.4、代码演示
1、抛出异常演示
public class BlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a")); // true
System.out.println(blockingQueue.add("b")); // true
System.out.println(blockingQueue.add("c")); // true
//System.out.println(blockingQueue.add("d")); // 抛异常
System.out.println(blockingQueue.element());// a,检查出即将出队列的元素
System.out.println(blockingQueue.remove()); // a
System.out.println(blockingQueue.remove()); // b
System.out.println(blockingQueue.remove()); // c
//System.out.println(blockingQueue.remove()); //抛异常
}
}
2、特殊值演示
public class BlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a")); // true
System.out.println(blockingQueue.offer("b")); // true
System.out.println(blockingQueue.offer("c")); // true
System.out.println(blockingQueue.offer("d")); // false
System.out.println(blockingQueue.peek()); // a,检查出即将出队列的元素
System.out.println(blockingQueue.poll()); // a
System.out.println(blockingQueue.poll()); // b
System.out.println(blockingQueue.poll()); // c
System.out.println(blockingQueue.poll()); // null
}
}
3、阻塞演示
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d"); //阻塞
System.out.println(blockingQueue.take()); // a
System.out.println(blockingQueue.take()); // b
System.out.println(blockingQueue.take()); // c
// System.out.println(blockingQueue.take()); // 阻塞
}
}
4、超时演示
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a", 3L, TimeUnit.SECONDS)); // true
System.out.println(blockingQueue.offer("b", 3L, TimeUnit.SECONDS)); // true
System.out.println(blockingQueue.offer("c", 3L, TimeUnit.SECONDS)); // true
System.out.println(blockingQueue.offer("d", 3L, TimeUnit.SECONDS)); // false,阻塞三秒后返回false
System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); // a
System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); // b
System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); // c
System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); // null,阻塞三秒后返回null
}
}
十一、线程池
11.1、简介
1、概述
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
2、线程池的优势
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
3、特点
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
- 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
4、继承图
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor 这几个类
11.2、功能线程池
Executors帮我们封装好的线程池,拿来即用
- Executors.newFixedThreadPool(5):一池5线程(定长线程池)
- Executors.newCachedThreadPool():自动分配线程数量(可缓存线程池)
- Executors.newSingleThreadExecutor(): 一池一线程(单线程化线程池)
public class Demo01 {
public static void main(String[] args) {
// 一池5线程
ExecutorService pool1 = Executors.newFixedThreadPool(5);
// 自动分配多少线程
ExecutorService pool2 = Executors.newCachedThreadPool();
// 一池一线程
ExecutorService pool3 = Executors.newSingleThreadExecutor();
try {
for (int i = 0; i <= 10; i++) {
pool1.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行成功");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool1.shutdown();
}
}
}
// 以一池5线程演示结果
// pool-1-thread-2 执行成功
// pool-1-thread-4 执行成功
// pool-1-thread-1 执行成功
// pool-1-thread-3 执行成功
// pool-1-thread-1 执行成功
// pool-1-thread-4 执行成功
// pool-1-thread-2 执行成功
// pool-1-thread-5 执行成功
// pool-1-thread-4 执行成功
// pool-1-thread-1 执行成功
// pool-1-thread-3 执行成功
底层都是new ThreadPoolExecutor
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
11.3、ThreadPoolExecutor的七个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数 | 说明 |
---|---|
int corePoolSize | 核心线程数 |
int maximumPoolSize | 最大线程数 |
long keepAliveTime | 空闲线程存活时间 |
TimeUnit unit | 存活的时间单位 |
BlockingQueue workQueue | 存放提交但未执行任务的队列 |
ThreadFactory threadFactory | 创建线程的工厂类 |
RejectedExecutionHandler handler | 等待队列满后的拒绝策略 |
11.4、执行流程
创建线程池时不会创建线程,执行run()
才会进行execute()
创建
假设最大线程数为5,核心线程为2,阻塞队列为3,现在进来9个线程
- 进来2个线程,调用线程池核心线程执行 (
1 2
) - 继续来三个线程(超过核心线程数),这三个就进入阻塞队列 (
3 4 5
) - 继续来三个线程(超过阻塞队列但未超过最大线程数),这三个线程会直接优先创建 (
6 7 8
) - 继续来线程(超过最大线程数),会通过拒绝策略进行拒绝 (
9 ...
)
11.5、拒绝策略(handler)
- AbortPolicy(默认):丢弃任务,并抛出拒绝执行 RejectedExecutionException
- CallerRunsPolicy:不会丢弃任务,也不会抛出异常,将任务返还给调用者执行
- DiscardPolicy:直接丢弃
- DiscardOldestPolicy:抛弃阻塞队列等待时间最久的任务,把当前任务加入队列中尝试再次提交当前任务
11.6、线程工厂
线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂:
/**
* The default thread factory.
*/
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}