线程概念
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
进程概念
进程是程序的基本执行实体
多线程概念
有了多线程,我们就可以让程序同时做多件事情,多线程的作用就是提高效率
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
多线程的实现方式
- 继承Thread类的方式
- 实现Runable接口的方式
- 利用Callable接口和Future接口方式
继承Thread类的方式实现
- 自定义一个类继承Thread
- 重写run方法
- 创建子类的对象,并启动线程
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"第"+i+"次打印Hello World");
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
/**
* 多线程第一种启动方式
* 1.自定义一个类继承Thread
* 2.重写run方法
* 3.创建子类的对象,并启动线程
*/
MyThread t1 = new MyThread();
t1.setName("线程1");
t1.start();
MyThread t2 = new MyThread();
t2.setName("线程2");
t2.start();
}
}
执行结果(截取部分)
线程2第73次打印Hello World
线程2第74次打印Hello World
线程1第55次打印Hello World
线程2第75次打印Hello World
线程2第76次打印Hello World
实现Runable接口的方式实现
- 自己定义一个类实现Runable接口
- 重写里面的run方法
- 创建自己的类的对象
- 创建一个Thread类的对象,并开启线程
public class MyRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"第"+i+"次打印Hello World");
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
//创建MyRun的对象
MyRun mr = new MyRun();
//创建第一个线程对象
Thread t1 = new Thread(mr);
//设置线程名字
t1.setName("线程1");
//开启第一个线程
t1.start();
//创建第二个线程对象
Thread t2 = new Thread(mr);
//设置线程名字
t2.setName("线程2");
//开启第一个线程
t2.start();
}
}
执行结果(截取部分)
线程1第52次打印Hello World
线程2第80次打印Hello World
线程2第81次打印Hello World
线程1第53次打印Hello World
线程2第82次打印Hello World
线程2第83次打印Hello World
利用Callable接口和Future接口方式实现
特点:可以获取到多线程运行的结果
- 创建一个类MyCallable实现Callable接口
- 重写call(是有返回值的,表示多线程运行的结果)
- 创建MyCallable的对象(表示多线程要执行的任务)
- 创建FutureTask的对象(作用管理多线程运行的结果)
- 创建Thread类的对象,并启动
//创建Thread类的对象
Thread t1 = new Thread(ft);
t1.setName("线程1");
//启动
t1.start();
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建Thread类的对象
Thread t1 = new Thread(ft);
t1.setName("线程1");
//启动
t1.start();
//创建Thread类的对象
Thread t2 = new Thread(ft);
t2.setName("线程2");
//启动
t2.start();
Integer result = ft.get();
System.out.println(result);
}
三种方式对比
优点 | 缺点 | |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可以扩展性交叉,不能再继承其他的类 |
实现Runnable接口 | 扩展性强,实现该接口的同时还能集成其他类 | 编程相对复杂,不能直接使用Thread类中的方法 |
实现Callable接口 | 可以获取多线程运行结果,扩展性强,实现该接口的同时还能集成其他类 | 编程相对复杂,不能直接使用Thread类中的方法 |
常见成员方法
方法名称 | 说明 |
---|---|
String getName() | 返回此线程的名称,默认设计Thread-X,从Threa-0开始 |
void setName(String name) | 设置线程的名字 |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级,默认的优先级是5,范围是1-10。 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
优先级,在java中多线程是抢占式调度,就是随机获取CPU的使用权,线程的优先级越高,获得使用权的概率越高,默认的优先级是5,范围是1-10。
守护线程
当非守护线程执行完毕之后,守护线程就没有执行下去的必要了,就会陆陆续续结束
应用场景:QQ聊天是非守护线程,文件传输时守护线程,如果聊天框关闭了,那么文件传输就结束了
public class Thread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"@"+i);
}
}
}
public class Thread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"@"+i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.setName("女神");
t2.setName("备胎");
//设置女神线程优先级为8
t1.setPriority(8);
//设置备胎线程为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
执行结果(截取部分)
备胎@72
女神@99
备胎@73
备胎@74
备胎@75
备胎@76
备胎@77
备胎@78
备胎@79
备胎@80
备胎@81
备胎@82
备胎@83
备胎@84
备胎@85
备胎@86
备胎@87
备胎@88
备胎@89
备胎@90Process finished with exit code 0
礼让线程
礼让线程会让线程执行尽量均衡
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.setName("飞机");
t2.setName("坦克");
t1.start();
t2.start();
}
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"@"+i);
//礼让线程
Thread.yield();
}
}
}
执行结果(截取部分)
飞机@0
坦克@0
飞机@1
坦克@1
坦克@2
飞机@2
坦克@3
插入线程
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("飞机");
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("main线程先执行");
}
}
执行结果
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
飞机@0
飞机@1
飞机@2
飞机@3
飞机@4
想让飞机线程插入到main线程之前执行
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("飞机");
t1.start();
//插入线程
t1.join();
for (int i = 0; i < 10; i++) {
System.out.println("main线程先执行");
}
}
执行结果
飞机@97
飞机@98
飞机@99
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
线程的生命周期
线程安全
需求:某电影院正在上映大片,共有100张票,电影院有3个窗口卖票,请设计一个程序模拟该电影院卖票
第一版
public class MyThread extends Thread{
int ticket = 0;
@Override
public void run() {
while (true){
if (ticket<100){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张瞟");
}else {
break;
}
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
这一版会出现每个窗口都卖出去100张票的问题,为了解决这个问题,可以在int ticket = 0;修改为static int ticket = 0;
public class MyThread extends Thread{
//解决多个窗口数据不同步的问题
static int ticket = 0;
@Override
public void run() {
while (true){
if (ticket<100){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张瞟");
}else {
break;
}
}
}
}
这一版也会出现问题,就是多个窗口卖出去同一张票,比如3号窗口和2号窗口同时卖出第4张票,最后窗口2和窗口3又多卖了两张票,出现了超出范围的票
执行结果
窗口3正在卖第2张瞟
窗口2正在卖第1张瞟
窗口1正在卖第3张瞟
窗口3正在卖第4张瞟
窗口2正在卖第4张瞟
窗口1正在卖第5张瞟…
窗口1正在卖第100张瞟
窗口2正在卖第101张瞟
窗口3正在卖第102张瞟
因为线程在执行代码时,CPU随时会被别的线程抢走,线程执行时,有随机性
为了解决这些BUG,我们需要把操作共享数据的代码锁起来
public class MyThread extends Thread{
//解决多个窗口数据不同步的问题
static int ticket = 0;
@Override
public void run() {
while (true){
//锁对象一定要唯一,类的字节码对象唯一
synchronized (MyThread.class){
if (ticket<100){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票");
}else {
break;
}
}
}
}
}
同步方法
概念:就是把synchronized关键字加到方法上
特点:
-
同步方法是锁住方法里面所有的代码
-
锁对象不能自己指定,
如果当前方法是非静态的,锁对象是this,是当前方法的调用者
如果是静态方法,那么锁对象是当前类的字节码文件对象
public class MyRun implements Runnable{
int ticket = 0;
@Override
public void run() {
//1.循环
while (true) {
//2.同步代码块(同步方法)
if (method())
break;
}
}
private synchronized boolean method(){
//3.判断共享数据是否超出上限
if (ticket == 100) {
return true;
} else {
//没有超出上限的业务代码
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
}
return false;
}
}
StringBuffer和StringBuilder
- StringBuffer里面的所有方法都加了synchronized关键字,因此是线程安全的
- StringBuilder里面的方法都没有加synchronized,因此是线程不安全的,效率高
//StringBuffer部分方法
@Override
public synchronized StringBuffer append(CharSequence s) {
toStringCache = null;
super.append(s);
return this;
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @since 1.5
*/
@Override
public synchronized StringBuffer append(CharSequence s, int start, int end)
{
toStringCache = null;
super.append(s, start, end);
return this;
}
//StringBuilder部分方法
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}
Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁
为了清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现ReentrantLock来实例化
public class MyThread extends Thread{
//解决多个窗口数据不同步的问题
static int ticket = 0;
//Lock是接口不能直接实例化,这里采用它的实现ReentrantLock来实例化
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//锁对象一定要唯一
lock.lock();
try {
if (ticket<100){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票");
}else {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//扫尾代码,不管系统如何执行,报错也罢,一定要解锁
lock.unlock();
}
}
}
}
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁
例如,两个食客来西餐厅吃牛排,此时餐厅还剩一把刀和一把叉子,只有同时拿到刀叉才能顺利进餐,此时食客A拿到了刀,食客B拿到了叉子,但是他们两个都不舍得放弃手中的餐具,又拿不到另一把需要的餐具,此时谁都吃不到牛排。
public class MyThread1 extends Thread{
static Object knife = new Object();
static Object fork = new Object();
@Override
public void run() {
while (true){
if ("食客A".equals(getName())){
synchronized (knife){
System.out.println("食客A拿到了knife,准备拿fork吃牛排");
synchronized (fork){
System.out.println("食客A拿到了knife和fork,顺利吃到了牛排");
}
}
}else if ("食客B".equals(getName())){
synchronized (fork){
System.out.println("食客B拿到了fork,准备拿knife吃牛排");
synchronized (knife){
System.out.println("食客B拿到了knife和fork,顺利吃到了牛排");
}
}
}
}
}
}
public class ThreadDemo7{
public static void main(String[] args) {
MyThread1 ta = new MyThread1();
MyThread1 tb = new MyThread1();
ta.setName("食客A");
tb.setName("食客B");
ta.start();
tb.start();
}
}
执行结果
食客A拿到了knife,准备拿fork吃牛排
食客B拿到了fork,准备拿knife吃牛排
生产者和消费者(等待唤醒机制)
生产者消费者常见方法
方法名称 | 说明 |
---|---|
void wait() | 当前线程等待,知道被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
生产者代码
/**
* 生产者厨师
*/
public class Cook extends Thread{
@Override
public void run() {
while (true){
synchronized (Desk.lock){
if (Desk.count==0){
break;
}else {
if (Desk.foodFlag==1){
//有食物,等待消费者吃饭
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//没有食物,开始做
System.out.println("厨师做了一份食物");
Desk.foodFlag = 1;
Desk.lock.notifyAll();
}
}
}
}
}
}
消费者代码
/**
* 消费者
*/
public class Foodie extends Thread{
@Override
public void run() {
while (true){
synchronized (Desk.lock){
//吃满十份,就不能再吃了
if (Desk.count==0){
break;
}else {
//判断桌子上是否有食物,如果有就开车,没有就等待,0表示没有
if (Desk.foodFlag==0){
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
Desk.lock.notifyAll();//唤醒跟这把锁绑定的线程
}else {
//如果有食物,就开吃
//把能吃的分数-1
Desk.count--;
System.out.println("吃货正在吃饭,还能再吃"+Desk.count+"份食物");
//吃完之后,唤醒厨师继续做饭
Desk.lock.notifyAll();
//修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
餐桌代码
/**
* 餐桌,控制生产者和消费者的执行
*/
public class Desk {
//定义桌子上是否有食物,1有,0没有
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
运行测试
public class ThreadDemo8 {
public static void main(String[] args) {
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.setName("厨师");
foodie.setName("吃货");
cook.start();
foodie.start();
}
}
控制台输出结果
厨师做了一份食物
吃货正在吃饭,还能再吃9份食物
厨师做了一份食物
吃货正在吃饭,还能再吃8份食物
厨师做了一份食物
吃货正在吃饭,还能再吃7份食物
厨师做了一份食物
吃货正在吃饭,还能再吃6份食物
厨师做了一份食物
吃货正在吃饭,还能再吃5份食物
厨师做了一份食物
吃货正在吃饭,还能再吃4份食物
厨师做了一份食物
吃货正在吃饭,还能再吃3份食物
厨师做了一份食物
吃货正在吃饭,还能再吃2份食物
厨师做了一份食物
吃货正在吃饭,还能再吃1份食物
厨师做了一份食物
吃货正在吃饭,还能再吃0份食物Process finished with exit code 0
阻塞队列失效生产者消费者
让阻塞队列容量为1,此时生产者生产一份,消费者就消费一份
生产者代码
public class Cook1 extends Thread{
ArrayBlockingQueue<String> queue;
public Cook1(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
try {
queue.put("食物");
System.out.println("厨师做了一份食物");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者代码
public class Foodie1 extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie1(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
try {
String food = queue.take();
System.out.println("吃货吃了一碗"+food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试结果
public class ThreadDemo9 {
public static void main(String[] args) {
ArrayBlockingQueue<String>queue = new ArrayBlockingQueue<>(1);
Cook1 c = new Cook1(queue);
Foodie1 f = new Foodie1(queue);
c.start();
f.start();
}
}
控制台输出结果
厨师做了一份食物
吃货吃了一碗食物
厨师做了一份食物
吃货吃了一碗食物
厨师做了一份食物
吃货吃了一碗食物
厨师做了一份食物
吃货吃了一碗食物
线程的状态
JVM管理线程六大状态,运行状态由操作系统管理
新建状态(new) | 创建线程对象 |
---|---|
就绪状态(RUNNABLE) | start() |
阻塞状态(BLOCKED) | 无法获得锁对象 |
等待状态(WAITING) | wait() |
计时等待(TIME_WAITING) | sleep() |
结束状态(TERMINATED) | 全部代码运行完毕 |
线程池
核心原理
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
线程池代码实现
Executors工具类,是线程池的工具类,通过调用不同的方法创建不同的线程池对象
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建一个上限很大的线程池,可视为没有上限的线程池 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
public class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 1; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
public class MyThreadPoolDemo {
public static void main(String[] args) {
//1.获取线程池对象,创建一个没有上限的线程池
ExecutorService pool1 = Executors.newCachedThreadPool();
//2.提交任务
pool1.submit(new MyRunnable1());
pool1.submit(new MyRunnable1());
pool1.submit(new MyRunnable1());
pool1.submit(new MyRunnable1());
pool1.submit(new MyRunnable1());
//3.销毁线程池
pool1.shutdown();
}
}
执行结果
pool-1-thread-2—1
pool-1-thread-2—2
pool-1-thread-4—1
pool-1-thread-4—2
pool-1-thread-2—3
pool-1-thread-4—3
pool-1-thread-3—1
pool-1-thread-3—2
pool-1-thread-2—4
pool-1-thread-3—3
pool-1-thread-3—4
pool-1-thread-3—5
pool-1-thread-3—6
pool-1-thread-3—7
pool-1-thread-3—8
pool-1-thread-3—9
pool-1-thread-4—4
pool-1-thread-4—5
pool-1-thread-4—6
pool-1-thread-4—7
pool-1-thread-4—8
pool-1-thread-4—9
pool-1-thread-5—1
pool-1-thread-5—2
pool-1-thread-5—3
pool-1-thread-5—4
pool-1-thread-5—5
pool-1-thread-5—6
pool-1-thread-5—7
pool-1-thread-5—8
pool-1-thread-5—9
pool-1-thread-1—1
pool-1-thread-2—5
pool-1-thread-1—2
pool-1-thread-1—3
pool-1-thread-2—6
pool-1-thread-1—4
pool-1-thread-2—7
pool-1-thread-1—5
pool-1-thread-2—8
pool-1-thread-1—6
pool-1-thread-2—9
pool-1-thread-1—7
pool-1-thread-1—8
pool-1-thread-1—9Process finished with exit code 0
线程池创建了5个线程去执行这些任务
创建一个有上限的线程池,演示线程池复用的情况
public class MyThreadPoolDemo {
public static void main(String[] args) {
//1.获取线程池对象,创建一个上限为3的线程池
ExecutorService pool1 = Executors.newFixedThreadPool(3);
//2.提交任务
pool1.submit(new MyRunnable1());
pool1.submit(new MyRunnable1());
pool1.submit(new MyRunnable1());
pool1.submit(new MyRunnable1());
pool1.submit(new MyRunnable1());
//3.销毁线程池
pool1.shutdown();
}
}
执行结果
pool-1-thread-2—1
pool-1-thread-2—2
pool-1-thread-2—3
pool-1-thread-2—4
pool-1-thread-2—5
pool-1-thread-2—6
pool-1-thread-2—7
pool-1-thread-2—8
pool-1-thread-2—9
pool-1-thread-3—1
pool-1-thread-3—2
pool-1-thread-3—3
pool-1-thread-3—4
pool-1-thread-3—5
pool-1-thread-3—6
pool-1-thread-3—7
pool-1-thread-3—8
pool-1-thread-3—9
pool-1-thread-3—1
pool-1-thread-3—2
pool-1-thread-3—3
pool-1-thread-3—4
pool-1-thread-3—5
pool-1-thread-3—6
pool-1-thread-3—7
pool-1-thread-3—8
pool-1-thread-1—1
pool-1-thread-3—9
pool-1-thread-2—1
pool-1-thread-2—2
pool-1-thread-2—3
pool-1-thread-2—4
pool-1-thread-2—5
pool-1-thread-2—6
pool-1-thread-1—2
pool-1-thread-2—7
pool-1-thread-2—8
pool-1-thread-2—9
pool-1-thread-1—3
pool-1-thread-1—4
pool-1-thread-1—5
pool-1-thread-1—6
pool-1-thread-1—7
pool-1-thread-1—8
pool-1-thread-1—9Process finished with exit code 0
thread-X 代表线程编号,当线程池容量上限为3时,执行5个任务,thread-X ,X为3
自定义线程池
定义时要定义
- 核心线程的数量
- 线程池中最大线程的数量
- 空闲时间(值),即临时线程空闲多长时间被弃用
- 空闲时间(单位),线程空闲时间的单位,如秒
- 阻塞队列,即排队的线程数
- 创建线程的方式
- 要执行的任务过多时的解决方案
核心线程为3,临时线程也为3,即线程池中最大的线程数量是6
自定义线程的工作流程
假如一开始只提交了三个任务,线程池就只创建3个线程去处理这3个任务。
如果提交了5个任务,线程池创建3个线程去处理3个任务,剩余的2个任务就会去排队等待,等有了空闲的线程,后面的2个任务才会去处理。
如果提交了8个任务,线程池创建3个线程去处理3个任务,如果定义了排队的队伍长度为3,那么第4,5,6个任务就会去排队等待,任务7.8才会创建临时线程去处理任务。即核心线程都被占用,排队的任务超过限定的队伍长度,才会创建临时线程。
如果提交了10个任务,线程池创建3个线程去处理3个任务,如果定义了排队的队伍长度为3,那么第4,5,6个任务就会去排队等待,任务7、8、9才会创建临时线程去处理任务,此时整个线程池已经是满负荷工作,核心线程、临时线程、阻塞队列全部被满额占用。那么超负荷的任务就会触发拒绝策略,默认的策略就是舍弃不要。另外还有其他的拒绝策略如下表。
任务拒绝策略
任务拒绝策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常,这是不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run方法绕过线程池直接执行 |
代码实现
public class MyRunnable1 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---");
}
}
public class MyThreadPoolDemo {
public static void main(String[] args) {
//获取线程池对象,核心线程数量为3,最大线程数量是6,空闲线程最大存活时间,时间单位,任务队列长队为3,拒绝策略
ThreadPoolExecutor pool2 = new ThreadPoolExecutor(3,//核心线程数量为3,
6,//最大线程数量是6
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列长队为3
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//拒绝策略,内部类
);
//提交10个任务
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.submit(new MyRunnable1());
pool2.shutdown();
}
}
执行结果分析,10个任务拒绝了1个,并抛出了rejectedExecution异常,线程编号thread-X,最大达到了6
Exception in thread “main” java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3b95a09c[Not completed, task = java.util.concurrent.Executors R u n n a b l e A d a p t e r @ 64 a 294 a 6 [ W r a p p e d t a s k = c o m . a t g u i g u . c l o u d . t h r e a d . M y R u n n a b l e 1 @ 7 e 0 b 37 b c ] ] r e j e c t e d f r o m j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r @ 6 a e 40994 [ R u n n i n g , p o o l s i z e = 6 , a c t i v e t h r e a d s = 6 , q u e u e d t a s k s = 3 , c o m p l e t e d t a s k s = 0 ] a t j a v a . b a s e / j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r RunnableAdapter@64a294a6[Wrapped task = com.atguigu.cloud.thread.MyRunnable1@7e0b37bc]] rejected from java.util.concurrent.ThreadPoolExecutor@6ae40994[Running, pool size = 6, active threads = 6, queued tasks = 3, completed tasks = 0] at java.base/java.util.concurrent.ThreadPoolExecutor RunnableAdapter@64a294a6[Wrappedtask=com.atguigu.cloud.thread.MyRunnable1@7e0b37bc]]rejectedfromjava.util.concurrent.ThreadPoolExecutor@6ae40994[Running,poolsize=6,activethreads=6,queuedtasks=3,completedtasks=0]atjava.base/java.util.concurrent.ThreadPoolExecutorAbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
at com.atguigu.cloud.thread.MyThreadPoolDemo.main(MyThreadPoolDemo.java:44)
pool-1-thread-2—
pool-1-thread-4—
pool-1-thread-2—
pool-1-thread-3—
pool-1-thread-2—
pool-1-thread-4—
pool-1-thread-1—
pool-1-thread-5—
pool-1-thread-6—