一、线程概述
- 线程是一个程序内部的一条执行流程
-
程序中如果只有一条执行流程,那这个程序就是单线程的程序
-
多线程是指从软硬件上实现的多条执行流程的技术,多条线程由 CPU 负责调度执行
- Java 通过 java.lang.Thread 类的对象来代表线程的
二、创建线程的方式
1、继承 Thread 类
(1)实现步骤
-
定义一个子类 MyThread 继承线程类 java.lang.Thread,重写 run 方法
-
创建 MyThread 类的对象
-
调用线程对象的 start 方法启动线程(启动后还是执行 run 方法的)
(2)注意事项
-
启动线程必须是调用 start 方法,不是调用 run 方法,直接调用 run 方法会当成普通方法执行,此时相当于单线程执行,只有调用 start 方法才是启动一个新的线程执行
-
不要把主线程任务放在启动子线程之前,否则主线程一直是先跑完的,相当于是一个单线程的效果
(3)具体实现
- MyThread.java
package com.my.threadcreate;// 1、继承 Thread 类
public class MyThread extends Thread{// 2、重写 Thread 类的 run 方法@Overridepublic void run() {// 线程执行任务for (int i = 0; i < 5; i++) {System.out.println("子线程 MyThread 输出:" + i);}}
}
- ThreadTest1.java
package com.my.threadcreate;public class ThreadTest1 {// main 方法是由一条默认的主线程执行public static void main(String[] args) {// 3、创建 MyThread 线程类代表一个线程MyThread myThread = new MyThread();// 4、启动线程myThread.start();for (int i = 0; i < 5; i++) {System.out.println("主线程 main 输出:" + i);}}
}
- 输出结果
子线程 MyThread 输出:0
主线程 main 输出:0
子线程 MyThread 输出:1
主线程 main 输出:1
子线程 MyThread 输出:2
主线程 main 输出:2
子线程 MyThread 输出:3
主线程 main 输出:3
子线程 MyThread 输出:4
主线程 main 输出:4
(4)优缺点
-
优点:编码简单
-
缺点:线程类已经继承 Thread,无法继承其他类,不利于功能的扩展
2、实现 Runnable 接口
(1)实现步骤
-
定义一个线程任务类 MyRunnable 实现 Runnable 接口,重写 run 方法
-
创建 MyRunnable 任务对象
-
把 MyRunnable 任务对象交给 Thread 处理
-
调用线程对象的 start 方法,启动线程
(2)具体实现
- MyRunnable.java
package com.my.threadcreate;// 1、继承 Runnable 接口
public class MyRunnable implements Runnable {// 2、重写 Runnable 接口的 run 方法@Overridepublic void run() {// 线程执行任务for (int i = 0; i < 5; i++) {System.out.println("子线程 MyRunnable 输出:" + i);}}
}
- ThreadTest2.java
package com.my.threadcreate;public class ThreadTest2 {public static void main(String[] args) {// 3、创建任务对象MyRunnable myRunnable = new MyRunnable();// 4、把任务对象交给一个线程对象处理new Thread(myRunnable).start();for (int i = 0; i < 5; i++) {System.out.println("主线程 main 输出:" + i);}}
}
- 输出结果
主线程 main 输出:0
子线程 MyRunnable 输出:0
主线程 main 输出:1
子线程 MyRunnable 输出:1
主线程 main 输出:2
子线程 MyRunnable 输出:2
主线程 main 输出:3
子线程 MyRunnable 输出:3
主线程 main 输出:4
子线程 MyRunnable 输出:4
(3)优缺点
-
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强
-
缺点:需要多一个 Runnable 对象
3、匿名内部类实现 Runnable 接口
(1)实现步骤
-
创建 Runnable 的匿名内部类对象
-
交给 Thread 线程对象
-
调用线程对象的 start 方法,启动线程
(2)具体实现
- ThreadTest3.java
package com.my.threadcreate;public class ThreadTest3 {public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("子线程输出:" + i);}}};new Thread(runnable).start();for (int i = 0; i < 5; i++) {System.out.println("主线程 main 输出:" + i);}}
}
- 输出结果
子线程输出:0
主线程 main 输出:0
子线程输出:1
主线程 main 输出:1
子线程输出:2
主线程 main 输出:2
子线程输出:3
主线程 main 输出:3
子线程输出:4
主线程 main 输出:4
(3)简写形式
new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("子线程输出:" + i);}}
}).start();
4、实现 Callable 接口
(1)问题引入
-
以前的线程创建方式都存在的一个问题:假如线程执行完毕后有一些数据需要返回,它们重写的 run 方法均不能直接返回结果
-
解决策略:JDK5.0 提供了 Callable 接口和 FutureTask 类来实现,这种方式最大的优点就是可以返回线程执行完毕后的结果
(2)实现步骤
-
定义一个类实现 Callable 接口,重写 call 方法,指定任务和要返回的数据
-
把 Callable 类型的对象装成 FutureTask 未来任务对象
-
把 FutureTask 未来任务对象交给 Thread 处理
-
调用线程对象的 start 方法,启动线程
-
线程执行完毕后、通过 FutureTask 未来任务对象的 get 方法获取线程任务执行的结果
(3)具体实现
- MyCallable.java
package com.my.threadcreate;import java.util.concurrent.Callable;// 1、实现 Callable 接口
public class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}// 2、重写 Callable 接口的 run 方法@Overridepublic String call() throws Exception {// 线程执行任务并返回结果int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return "子线程 MyCallable 求出 1 - " + n + " 的和是:" + sum;}
}
- ThreadTest4.java
package com.my.threadcreate;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class ThreadTest4 {public static void main(String[] args) {// 3、创建一个 MyCallable 对象MyCallable myCallable = new MyCallable(100);// 4、将 MyCallable 对象封装成一个 FutureTask 未来任务对象// FutureTask 未来任务对象的作用// (1)是一个任务对象,实现了 Runnable 接口// (2)可以在线程执行完毕之后调用 get 方法获取线程执行完毕后的结果FutureTask<String> futureTask = new FutureTask<>(myCallable);// 5、把 FutureTask 未来任务对象交给 Thread 对象new Thread(futureTask).start();try {// 6、获取线程执行完毕后返回的结果System.out.println(futureTask.get());} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}}
}
- 输出结果
子线程 MyCallable 求出 1 - 100 的和是:5050
(4)补充
- ThreadTest4_.java,FutureTask 未来任务对象 get 方法的执行策略
package com.my.threadcreate;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class ThreadTest4_ {public static void main(String[] args) {MyCallable myCallable1 = new MyCallable(100);MyCallable myCallable2 = new MyCallable(10);FutureTask<String> futureTask1 = new FutureTask<>(myCallable1);FutureTask<String> futureTask2 = new FutureTask<>(myCallable2);new Thread(futureTask2).start();new Thread(futureTask1).start();try {// 获取线程执行完毕后返回的结果// 如果执行到这,假如上面的线程还没有执行完毕,这里会暂停,等待上面的线程执行完毕System.out.println(futureTask1.get());System.out.println(futureTask2.get());} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}}
}
- 输出结果
子线程 MyCallable 求出 1 - 10000 的和是:50005000
子线程 MyCallable 求出 1 - 10 的和是:55
(5)优缺点
-
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强,且可以在线程执行完毕后去获取线程执行的结果
-
缺点:编码复杂一点
三、Thread 常用方法
1、基本介绍
2、演示
- MyThread.java
package com.my.threadmethod;public class MyThread extends Thread{public MyThread(String name) {setName(name);}@Overridepublic void run() {Thread thread = Thread.currentThread();for (int i = 0; i < 5; i++) {System.out.println("子线程 " + thread.getName() + " 输出:" + i);}}
}
- ThreadMethodTest1.java
package com.my.threadmethod;public class ThreadMethodTest1 {public static void main(String[] args) {MyThread myThread1 = new MyThread("thread001");
// myThread1.setName("thread1");myThread1.start();// 子线程名字System.out.println(myThread1.getName());MyThread myThread2 = new MyThread("thread002");
// myThread1.setName("thread2");myThread2.start();System.out.println(myThread2.getName());Thread thread = Thread.currentThread();// 主线程的名字System.out.println(thread.getName());for (int i = 0; i < 5; i++) {System.out.println("主线程 main 输出:" + i);}}
}
- 输出结果
thread001
thread002
main
主线程 main 输出:0
主线程 main 输出:1
主线程 main 输出:2
主线程 main 输出:3
主线程 main 输出:4
子线程 thread002 输出:0
子线程 thread001 输出:0
子线程 thread002 输出:1
子线程 thread001 输出:1
子线程 thread002 输出:2
子线程 thread001 输出:2
子线程 thread002 输出:3
子线程 thread001 输出:3
子线程 thread002 输出:4
子线程 thread001 输出:4
- ThreadMethodTest2.java
package com.my.threadmethod;public class ThreadMethodTest2 {public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 5; i++) {if (i == 3) {// 让当前执行线程暂停 5 秒Thread.sleep(5 * 1000);}System.out.println("主线程 main 输出:" + i);}MyThread myThread1 = new MyThread("thread001");myThread1.start();myThread1.join();MyThread myThread2 = new MyThread("thread002");myThread2.start();myThread2.join();}
}
- 输出结果
主线程 main 输出:0
主线程 main 输出:1
主线程 main 输出:2
主线程 main 输出:3
主线程 main 输出:4
子线程 thread001 输出:0
子线程 thread001 输出:1
子线程 thread001 输出:2
子线程 thread001 输出:3
子线程 thread001 输出:4
子线程 thread002 输出:0
子线程 thread002 输出:1
子线程 thread002 输出:2
子线程 thread002 输出:3
子线程 thread002 输出:4
四、线程安全
1、基本介绍
- 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题
2、举例说明
- 小明和小红是一对夫妻,他们有一个共同的账户,余额是 10 万元,小明和小红同时来取钱,并且两人各自都取钱 10 万元,他们取钱行为如下
-
判断余额是否足够
-
吐出 10 万元
-
更新账户余额
- 这样的行为存在的线程安全问题
小明线程 | 小红线程 |
---|---|
判断余额是否足够,足够 | - |
- | 判断余额是否足够,足够 |
吐出 10 万元 | - |
更新账户余额,余额 0 | - |
- | 吐出 10 万元 |
- | 更新账户余额,余额 -10 * 1000 |
3、模拟演示
(1)实现思路
-
提供一个账户类,创建一个账户对象代表两人的共享账户
-
定义一个线程类,用于创建两个线程,分别代表小明和小红
-
创建两个线程,传入同一个账户对象给这两个线程处理
-
启动这两个线程,同时去同一个账户对象中取钱 10 万
(2)具体实现
- Account.java
package com.my.threadsafe;public class Account {private String cardId; // 卡号private double money; // 余额public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}// 取钱public void drawMoney(int m) {String name = Thread.currentThread().getName();// 判余额是否足够if (this.money >= m) {System.out.println(name + " 来取钱,取 " + m);this.money -= m;System.out.println(name + " 取钱完成,余额 " + money);} else {System.out.println(name + " 来取钱,余额不足");}}
}
- DrawThread.java
package com.my.threadsafe;public class DrawThread extends Thread{private Account account;public DrawThread(Account account, String name) {super(name);this.account = account;}@Overridepublic void run() {super.run();account.drawMoney(10 * 10000);}
}
- ThreadTest.java
package com.my.threadsafe;public class ThreadTest {public static void main(String[] args) {// 1、创建一个账户对象,代表两个人的共享账户Account account = new Account("abcd", 10 * 10000);// 2、创建两个线程,分别代表小明和小红,去同一个账户取钱 10 万new DrawThread(account, "小明").start();new DrawThread(account, "小红").start();}
}
- 输出结果
小红 来取钱,取 100000
小明 来取钱,取 100000
小红 取钱完成,余额 0.0
小明 取钱完成,余额 -100000.0
四、线程同步
1、基本介绍
-
线程同步:解决线程安全问题的方案
-
线程同步思想:让多个线程实现先后依次访问共享资源
-
线程同步的常见方案:加锁,每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来
2、同步代码块
(1)基本介绍
synchronized (【同步锁】) {【访问共享资源的核心代码】
}
-
作用:把访问共享资源的核心代码给上锁,以此保证线程安全
-
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
-
注:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出 Bug
(2)具体实现
public void drawMoney(int m) {String name = Thread.currentThread().getName();synchronized ("Hello World") {if (this.money >= m) {System.out.println(name + " 来取钱,取 " + m);this.money -= m;System.out.println(name + " 取钱完成,余额 " + money);} else {System.out.println(name + " 来取钱,余额不足");}}
}
- 测试输出结果
小明 来取钱,取 100000
小明 取钱完成,余额 0.0
小红 来取钱,余额不足
(3)修改实现
- 上例中的锁对象“Hello World”不光可以锁住小明或小红线程,也会锁住其他线程
Account newAccount = new Account("abcd", 10 * 10000);
new DrawThread(newAccount, "jack").start();
new DrawThread(newAccount, "tom").start();
- 所以该锁对象的选择有问题,锁的范围太大了,需要修改
public void drawMoney(int m) {String name = Thread.currentThread().getName();synchronized (this) {if (this.money >= m) {System.out.println(name + " 来取钱,取 " + m);this.money -= m;System.out.println(name + " 取钱完成,余额 " + money);} else {System.out.println(name + " 来取钱,余额不足");}}
}
(4)补充
- 锁对象随便选择一个唯一的对象可能会影响其他无关线程的执行
- 建议使用共享资源作为锁对象,对于实例方法建议使用 this 作为锁对象
public void test() {synchronized (this) {}
}
- 对于静态方法建议使用字节码(【类名】.class)对象作为锁对象
public static void test() {synchronized (Account.class) {}
}
3、同步方法
(1)基本介绍
【修饰符】 synchronized 【返回值类型】 【方法名称】(【形参列表】) {【访问共享资源的核心代码】
}
-
作用:把访问共享资源的核心方法给上锁,以此保证线程安全
-
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
(2)具体实现
public synchronized void drawMoney(int m) {String name = Thread.currentThread().getName();if (this.money >= m) {System.out.println(name + " 来取钱,取 " + m);this.money -= m;System.out.println(name + " 取钱完成,余额 " + money);} else {System.out.println(name + " 来取钱,余额不足");}
}
- 测试输出结果
小明 来取钱,取 100000
小明 取钱完成,余额 0.0
小红 来取钱,余额不足
(3)底层原理
- 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码
-
如果方法是实例方法,同步方法默认用 this 作为的锁对象
-
如果方法是静态方法:同步方法默认用字节码(【类名】.class)作为的锁对象
(4)同步代码块对比同步方法
-
范围上:同步代码块锁的范围更小,同步方法锁的范围更大
-
可读性:同步方法更好
4、Lock
(1)基本介绍
-
Lock 锁是 JDK5 开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
-
Lock 是接口,不能直接实例化,可以采用它的实现类 ReentrantLock 来构建 Lock 锁对象
构造器 | 说明 |
---|---|
public ReentrantLock() | 获取 Lock 锁实现类对象 |
方法 | 说明 |
---|---|
void lock() | 获取锁 |
void unlock() | 释放锁 |
(2)具体实现
// 这里建议使用 final 关键字修饰,因为一个账户对象中的锁对象是唯一的,且不能进行替换
private final Lock lock = new ReentrantLock();public void drawMoney(int m) {String name = Thread.currentThread().getName();// 加锁lock.lock();if (this.money >= m) {System.out.println(name + " 来取钱,取 " + m);this.money -= m;System.out.println(name + " 取钱完成,余额 " + money);} else {System.out.println(name + " 来取钱,余额不足");}// 解锁lock.unlock();
}
- 测试输出结果
小明 来取钱,取 100000
小明 取钱完成,余额 0.0
小红 来取钱,余额不足
(3)存在问题
- 一旦加锁后的程序产生异常且没有进行异常处理,就会导致没有机会解锁
public void drawMoney(int m) {String name = Thread.currentThread().getName();// 加锁lock.lock();if (this.money >= m) {System.out.println(name + " 来取钱,取 " + m);this.money -= m;System.out.println(name + " 取钱完成,余额 " + money);// 这行代码会导致异常int num = 10 / 0;} else {System.out.println(name + " 来取钱,余额不足");}// 解锁lock.unlock();
}
- 测试输出结果
小明 来取钱,取 100000
小明 取钱完成,余额 0.0
Exception in thread "小明" java.lang.ArithmeticException: / by zero
(4)修改实现
public void drawMoney(int m) {String name = Thread.currentThread().getName();// 加锁lock.lock();try {if (this.money >= m) {System.out.println(name + " 来取钱,取 " + m);this.money -= m;System.out.println(name + " 取钱完成,余额 " + money);// 这行代码会导致异常int num = 10 / 0;} else {System.out.println(name + " 来取钱,余额不足");}} catch (Exception e) {e.printStackTrace();} finally {// 解锁lock.unlock();}
}
- 测试输出结果
小明 来取钱,取 100000
小明 取钱完成,余额 0.0
小红 来取钱,余额不足
java.lang.ArithmeticException: / by zero
五、线程通信
1、基本介绍
- 当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺
2、线程通信的常见模型
- 线程通信的常见模型有生产者与消费者模型
-
生产者线程负责生产数据
-
消费者线程负责消费生产者生产的数据
- 生产者生产完数据应该等待自己,通知消费者消费,消费者消费完数据也应该等待自己,通知生产者生产
3、具体实现
(1)需求
(2)实现代码
- Desk
package com.my.threadcommunication;import java.util.ArrayList;
import java.util.List;public class Desk {private List<String> baoZiList = new ArrayList<>();// 这两个方法都是用 this 作为锁,所以可以锁住五个线程// 放一个包子// 三个生产者共享public synchronized void put() {try {String name = Thread.currentThread().getName();if (baoZiList.size() == 0) {baoZiList.add(name + " 做的包子");System.out.println(name + " 做了一个包子");Thread.sleep(2000);}// 唤醒别人,等待自己this.notifyAll();this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}// 拿一个包子// 两个消费者共享public synchronized void get() {try {String name = Thread.currentThread().getName();if (baoZiList.size() == 1) {System.out.println(name + " 吃了 " + baoZiList.get(0));baoZiList.clear();Thread.sleep(2000);}// 唤醒别人,等待自己this.notifyAll();this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
- ThreadCommunicationTest.java
package com.my.threadcommunication;public class ThreadCommunicationTest {public static void main(String[] args) {Desk desk = new Desk();// 创建三个生产者new Thread(() -> {while (true) {desk.put();}}, "厨师 1").start();new Thread(() -> {while (true) {desk.put();}}, "厨师 2").start();new Thread(() -> {while (true) {desk.put();}}, "厨师 3").start();// 创建两个消费者new Thread(() -> {while (true) {desk.get();}}, "吃货 1").start();new Thread(() -> {while (true) {desk.get();}}, "吃货 2").start();}
}
4、补充
- Object 类的等待和唤醒方法
方法 | 说明 |
---|---|
void wait() | 让当前线程等待并释放所占锁,直到另一个线程调用 notify 方法或 notifyAll 方法 |
void notify() | 唤醒正在等待的单个线程 |
void notifyAll() | 唤醒正在等待的所有线程 |
- 注:上述方法应该使用当前同步锁对象进行调用
六、线程池引入
1、概述
-
线程池就是一个可以复用线程的技术,例如,在系统中创建出三个线程,这三个线程处理完任务后可以继续复用这三个线程去处理其他任务,即这三个线程会长久的存活重复的利用
-
不使用线程池的问题:用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能
2、工作原理
- 以用户发起请求为例,并不是用户发起请求,即来一个任务就创建一个线程,线程池中有一块区域用于一些固定数量的线程,线程池中还有一块区域用于存放任务
-
假如我们的固定数量的线程有 3 个,此时有一个任务过来了,就会出现一个线程去处理这个任务
-
如果达到了线程处理任务的上线,即 3 个线程此时都在处理任务,那么新的任务就会进行排队
-
假如一个线程处理完任务了,它就可以去处理下一个任务
- 注:线程池既可以控制线程的数量,也能控制任务的数量,这样就能避免资源耗尽的风险
3、概念理解
-
线程池中的线程称之为工作线程或核心线程(Work Thread),它们是固定数量,可以重复利用的
-
线程池中的任务存放在任务队列(Work Queue),这些任务其实就是对象,它必须是实现 Runnable 或 Callable 接口的
七、线程池使用
1、引入
- JDK5.0 起提供了代表线程池的接口 ExecutorService
-
使用 ExecutorService 的实现类 ThreadPoolExecutor 自己创建一个线程池对象
-
使用线程池的工具类 Executors 调用方法返回不同特点的线程池对象
2、线程池创建
(1)基本介绍
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
参数 | 说明 |
---|---|
corePoolSize | 指定线程池的核心线程的数量 |
maximumPoolSize | 指定线程池的最大线程数量 |
keepAliveTime | 指定临时线程的存活时间 |
unit | 指定临时线程存活的时间单位(秒、分、时、天) |
workQueue | 指定线程池的任务队列 |
threadFactory | 指定线程池的线程工厂 |
handler | 指定线程池的任务拒绝策略,即当线程都在忙且任务队列也满了时,新任务来了该如何处理 |
- 类比现实场景
参数 | 说明 |
---|---|
corePoolSize | 正式工有 3 人 |
maximumPoolSize | 最大员工数有 5 人,临时工有 2 人 |
keepAliveTime | 临时工空闲多久被开除 |
unit | - |
workQueue | 客人排队的地方 |
threadFactory | 负责招聘员工的人事 |
handler | 忙不过来咋办 |
(2)具体实现
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
(3)注意事项
-
临时线程什么时候创建?新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
-
什么时候会开始拒绝新任务?核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
3、处理 Runnable 任务
(1)基本介绍
(2)具体实现
- RunnableTask.java
package com.my.threadpool;public class RunnableTask implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " Hello World");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
- 测试代码
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());RunnableTask runnableTask = new RunnableTask();
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 复用前面的核心线程
pool.execute(runnableTask); // 复用前面的核心线程
- 输出结果
pool-1-thread-1 Hello World
pool-1-thread-2 Hello World
pool-1-thread-3 Hello World
pool-1-thread-2 Hello World
pool-1-thread-1 Hello World
(3)关闭线程池
- 注:线程池默认不会关闭,里面的线程会长久存活为系统不断提供服务的,在实际开发中,一般不会关闭线程池
- 等待全部任务执行完毕后关闭线程池
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());RunnableTask runnableTask = new RunnableTask();
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 复用前面的核心线程
pool.execute(runnableTask); // 复用前面的核心线程pool.shutdown(); // 等待线程池的任务全部执行完毕后,再关闭线程池
- 输出结果
pool-1-thread-1 Hello World
pool-1-thread-3 Hello World
pool-1-thread-2 Hello World
pool-1-thread-1 Hello World
pool-1-thread-3 Hello World
- 立即关闭线程池
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());RunnableTask runnableTask = new RunnableTask();
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 复用前面的核心线程
pool.execute(runnableTask); // 复用前面的核心线程pool.shutdownNow(); // 立即关闭线程池
- 输出结果
pool-1-thread-1 Hello World
pool-1-thread-2 Hello World
pool-1-thread-3 Hello World
Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-3" java.lang.RuntimeException: java.lang.InterruptedException: sleep interruptedat com.my.threadpool.RunnableTask.run(RunnableTask.java:11)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interruptedat java.base/java.lang.Thread.sleep(Native Method)at com.my.threadpool.RunnableTask.run(RunnableTask.java:9)... 3 more
java.lang.RuntimeException: java.lang.InterruptedException: sleep interruptedat com.my.threadpool.RunnableTask.run(RunnableTask.java:11)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interruptedat java.base/java.lang.Thread.sleep(Native Method)at com.my.threadpool.RunnableTask.run(RunnableTask.java:9)... 3 more
java.lang.RuntimeException: java.lang.InterruptedException: sleep interruptedat com.my.threadpool.RunnableTask.run(RunnableTask.java:11)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interruptedat java.base/java.lang.Thread.sleep(Native Method)at com.my.threadpool.RunnableTask.run(RunnableTask.java:9)... 3 more
(4)临时线程的创建时机
- RunnableTask.java,延长休眠时间以模拟该任务需要较长时间处理
package com.my.threadpool;public class RunnableTask implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " Hello World");try {Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
- 测试代码,此时 3 个核心线程在忙,任务队列占满了,再出现新任务的时候就会创建临时线程
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());RunnableTask runnableTask = new RunnableTask();
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务pool.execute(runnableTask);
pool.execute(runnableTask);
pool.execute(runnableTask);
pool.execute(runnableTask);// 到达临时线程创建的时机
pool.execute(runnableTask);
pool.execute(runnableTask);
- 输出结果
pool-1-thread-3 Hello World
pool-1-thread-2 Hello World
pool-1-thread-4 Hello World
pool-1-thread-5 Hello World
pool-1-thread-1 Hello World
4、任务的拒绝策略
(1)基本介绍
- 当核心线程在忙,任务队列占满了,临时线程也在忙,再出现新任务的时候就会启用拒绝策略
(2)演示
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());RunnableTask runnableTask = new RunnableTask();
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务
pool.execute(runnableTask); // 线程池会自动创建一个新线程,自动处理这个任务pool.execute(runnableTask);
pool.execute(runnableTask);
pool.execute(runnableTask);
pool.execute(runnableTask);// 到达临时线程创建的时机
pool.execute(runnableTask);
pool.execute(runnableTask);// 启用拒绝策略
pool.execute(runnableTask);
- 输出结果
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.my.threadpool.RunnableTask@2d98a335 rejected from java.util.concurrent.ThreadPoolExecutor@16b98e56[Running, pool size = 5, active threads = 5, queued tasks = 4, completed tasks = 0]at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.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 com.my.threadpool.ThreadPoolTest.test4(ThreadPoolTest.java:82)at com.my.threadpool.ThreadPoolTest.main(ThreadPoolTest.java:10)
pool-1-thread-2 Hello World
pool-1-thread-1 Hello World
pool-1-thread-5 Hello World
pool-1-thread-4 Hello World
pool-1-thread-3 Hello World
5、处理 Callable 任务
(1)基本介绍
(2)具体实现
- CallableTask.java
package com.my.threadpool;import java.util.concurrent.Callable;public class CallableTask implements Callable<String> {private int n;public CallableTask(int n) {this.n = n;}@Overridepublic String call() throws Exception {// 线程执行任务并返回结果int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return Thread.currentThread().getName() + " 求出 1 - " + n + " 的和是:" + sum;}
}
- 测试代码
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());CallableTask callableTask = new CallableTask(100);
Future<String> f1 = pool.submit(callableTask);
Future<String> f2 = pool.submit(callableTask);
Future<String> f3 = pool.submit(callableTask);
Future<String> f4 = pool.submit(callableTask);try {System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());
} catch (InterruptedException e) {throw new RuntimeException(e);
} catch (ExecutionException e) {throw new RuntimeException(e);
}
- 输出结果
pool-1-thread-1求出 1 - 100 的和是:5050
pool-1-thread-2求出 1 - 100 的和是:5050
pool-1-thread-3求出 1 - 100 的和是:5050
pool-1-thread-2求出 1 - 100 的和是:5050
6、Executors 创建线程池
(1)基本介绍
- 注:这些方法的底层都是通过线程池的实现类 ThreadPoolExecutor 来创建线程池对象的
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
(2)具体实现
- 创建固定数量的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);CallableTask callableTask = new CallableTask(100);
Future<String> f1 = pool.submit(callableTask);
Future<String> f2 = pool.submit(callableTask);
Future<String> f3 = pool.submit(callableTask);
Future<String> f4 = pool.submit(callableTask);try {System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());
} catch (InterruptedException e) {throw new RuntimeException(e);
} catch (ExecutionException e) {throw new RuntimeException(e);
}
- 输出结果
pool-1-thread-1 求出 1 - 100 的和是:5050
pool-1-thread-2 求出 1 - 100 的和是:5050
pool-1-thread-3 求出 1 - 100 的和是:5050
pool-1-thread-3 求出 1 - 100 的和是:5050
- 创建一个线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();CallableTask callableTask = new CallableTask(100);
Future<String> f1 = pool.submit(callableTask);
Future<String> f2 = pool.submit(callableTask);
Future<String> f3 = pool.submit(callableTask);
Future<String> f4 = pool.submit(callableTask);try {System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());
} catch (InterruptedException e) {throw new RuntimeException(e);
} catch (ExecutionException e) {throw new RuntimeException(e);
}
- 输出结果
pool-1-thread-1 求出 1 - 100 的和是:5050
pool-1-thread-1 求出 1 - 100 的和是:5050
pool-1-thread-1 求出 1 - 100 的和是:5050
pool-1-thread-1 求出 1 - 100 的和是:5050
(3)注意事项
- 大型并发系统环境中使用 Executors 如果不注意可能会出现系统风险
7、任务执行顺序
(1)基本介绍
-
线程池中的任务执行顺序并不是按照提交任务的顺序进行的,而是由线程池的调度算法来决定
-
如果想要使他们顺序执行,可以使用 newSingleThreadExecutor 或设置 ThreadPoolExecutor 核心线程数为 1
(2)演示
- MyOrderRunnable.java
package com.my.threadpool;public class MyOrderRunnable implements Runnable {private int num;public MyOrderRunnable(int num) {this.num = num;}@Overridepublic void run() {System.out.println(num);}
}
- 测试线程池执行任务的无序性
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i = 1; i <= 10; i++) {pool.execute(new MyOrderRunnable(i));
}
- 输出结果
1
3
2
5
7
8
10
9
4
6
- 使用 newSingleThreadExecutor,有序执行任务
ExecutorService executorService = Executors.newSingleThreadExecutor();for (int i = 1; i <= 10; i++) {executorService.execute(new MyOrderRunnable(i));
}
- 输出结果
1
2
3
4
5
6
7
8
9
10
- 设置 ThreadPoolExecutor 核心线程数为 1, 有序执行任务
ExecutorService pool = new ThreadPoolExecutor(1, 5, 8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1000), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i = 1; i <= 10; i++) {pool.execute(new MyOrderRunnable(i));
}
- 输出结果
1
2
3
4
5
6
7
8
9
10
8、核心线程的数量配置
-
计算密集型的任务:核心线程数量 = CPU 的核数 + 1
-
IO 密集型的任务(读文件、通信):核心线数量 = CPU 核数 * 2
八、并发与并行
1、进程
-
正在运行的程序(软件)就是一个独立的进程
-
线程是属于进程的,一个进程中可以同时运行很多个线程
-
进程中的多个线程其实是并发和并行执行的
2、并发
- 进程中的线程是由 CPU 负责调度执行的,但 CPU 能同时处理线程的数量有限,为了保证全部线程都能往前执行 CPU 会轮询为系统的每个线程服务,由于 CPU 切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发
3、并行
- 在同一个时刻上,同时有多个线程在被 CPU 调度执行
九、线程的生命周期
1、生命周期引入
-
线程的生命周期也就是线程从生到死的过程中,经历的各种状态及状态转换
-
Java 总共定义了 6 种状态,它们都定义在 Thread 类的内部枚举类中
public class Thread implements Runnable {...public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;}...
}
2、6 种状态的互相转换
十、乐观锁与悲观锁
1、基本介绍
-
悲观锁:一上来就加锁,没有安全感,每次只能一个线程进入访问完毕后再解锁, 线程安全,性能较差
-
乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制,线程安全,性能较好
2、案例引入
(1)线程安全问题回顾
- MyRunnable.java
package com.my.happybluelock;public class MyRunnable implements Runnable{private int count;@Overridepublic void run() {for (int i = 0; i < 1000; i++) {count++;System.out.println("count:" + count);}}
}
- ThreadSafeTest.java
package com.my.happybluelock;public class ThreadSafeTest {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();for (int i = 0; i < 100; i++) {new Thread(myRunnable).start();}}
}
- 输出结果
...
count:99995
count:99996
count:99997
count:99998
count:99999
(2)案例分析
线程 1 | 线程 2 |
---|---|
取出 count 的值,此时 count 为 10 | - |
- | 取出 count 的值,此时 count 为 10 |
进行自增操作 | 进行自增操作 |
将结果 11 给 count | 将结果 11 给 count |
3、处理策略
(1)基本介绍
-
悲观锁处理:使用 synchronized 关键字或 synchronized 同步代码块
-
乐观锁处理:
(2)乐观锁原理简述
线程 1 | 线程 2 |
---|---|
取出 count 的值,此时 count 为 10 同时记录 count 的原始值为 10 | - |
- | 取出 count 的值,此时 count 为 10 同时记录 count 的原始值为 10 |
进行自增操作 | 进行自增操作 |
检查现在的 count 是否为原始值 10 是,将结果 11 给 count | - |
- | 检查现在的 count 是否为原始值 10 不是,则放弃操作 |
(2)具体实现
- 悲观锁处理
package com.my.happybluelock;public class MyRunnable implements Runnable{private int count;@Overridepublic void run() {for (int i = 0; i < 1000; i++) {synchronized (this) {count++;System.out.println("count:" + count);}}}
}
- 乐观锁处理
package com.my.happybluelock;import java.util.concurrent.atomic.AtomicInteger;public class MyRunnable implements Runnable{// 整数修改的乐观锁,使用原子类实现private AtomicInteger count = new AtomicInteger();@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("count:" + count.incrementAndGet());}}
}