什么是线程?
线程(thread)是一个程序内部的一条执行路径。
我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。
public static void main(String[] args) {
// 执行代码
}
程序中如果只有一条执行路径,那么这个程序就是单线程的程序。
什么是多线程?
多线程用在哪里,有什么好处?
关于多线程需要学会什么?
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
PrimeThread p = new PrimeThread(143);
p.start();
Thread类:
多线程的实现方案一:继承Thread类
Test.java
/**
* 目标:多线程的创建方式一:继承Thread类实现
*/
public class Test {
public static void main(String[] args) {
// 3.new一个新线程对象
Thread thread = new MyThread();
// 4.调用start方法启动线程(执行的还是run方法)
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
/**
* 1.自定义线程类
*/
class MyThread extends Thread {
/**
* 2.重写run方法 里面是定义线程之后做什么
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程执行输出:" + i);
}
}
}
子线程执行输出:0
主线程执行输出:0
主线程执行输出:1
主线程执行输出:2
主线程执行输出:3
子线程执行输出:1
主线程执行输出:4
主线程执行输出:5
主线程执行输出:6
主线程执行输出:7
主线程执行输出:8
主线程执行输出:9
子线程执行输出:2
子线程执行输出:3
子线程执行输出:4
子线程执行输出:5
子线程执行输出:6
子线程执行输出:7
子线程执行输出:8
子线程执行输出:9
方式一优点和缺点:
总结:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
实现Runnable接口:
Thread构造器:
构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称。 |
public Thread(Runnable target) | 封装Runnable对象称为线程对象。 |
public Thread(Runnable target, String name) | 封装Runnable对象成为线程对象,并指定线程名称。 |
Test.java
/**
* 目标:学会线程的创建方式二 理解它的优缺点
*/
public class Test {
public static void main(String[] args) {
// 3.创建一个任务对象
Runnable runnable = new MyRunnable();
// 4.把任务对象交给Thread处理
Thread thread = new Thread(runnable);
// 5.启动线程
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
/**
* 1.定义一个线程任务类 实现Runnable接口
*/
class MyRunnable implements Runnable {
/**
* 2.重写run方法 定义线程的执行任务
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程执行输出:" + i);
}
}
}
主线程执行输出:0
子线程执行输出:0
主线程执行输出:1
主线程执行输出:2
主线程执行输出:3
主线程执行输出:4
主线程执行输出:5
主线程执行输出:6
主线程执行输出:7
主线程执行输出:8
主线程执行输出:9
子线程执行输出:1
子线程执行输出:2
子线程执行输出:3
子线程执行输出:4
子线程执行输出:5
子线程执行输出:6
子线程执行输出:7
子线程执行输出:8
子线程执行输出:9
方式二优点和缺点:
实现Runnable接口:匿名内部类形式
Test.java
/**
* 目标:学会线程的创建方式二 匿名内部类的方式实现 语法形式
*/
public class Test {
public static void main(String[] args) {
// 1.创建Runnable的匿名内部类对象
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程1执行输出:" + i);
}
}
};
// 2.交给线程对象
Thread thread = new Thread(runnable);
// 3.启动线程
thread.start();
// 简化版一
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程简化版一执行输出:" + i);
}
}
}).start();
// 简化版二
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("子线程简化版二执行输出:" + i);
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
前两种线程创建方式都存在的问题:
解决方案:
利用Callable、FutureTask接口实现:
FutureTask的API:
方法名称 | 说明 |
---|---|
public FutureTask<>(Callable call) | 把Callable对象封装成FutureTask对象。 |
public V get() throws Exception | 获取线程执行call()方法返回的结果。 |
Test.java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 目标:学会线程的创建方式三 实现Callable接口、结合FutureTask完成
*/
public class Test {
public static void main(String[] args) {
// 3.创建Callable任务对象
Callable<String> callable = new MyCallable(100);
// 4.用FutureTask把Callable对象封装成线程任务对象
// FutureTask实现了Runnable接口
// FutureTask可以在线程执行完毕之后通过调用其get()方法得到线程执行完成的结果
FutureTask<String> futureTask = new FutureTask<>(callable);
// 5.把线程任务对象交给Thread处理
Thread thread = new Thread(futureTask);
// 6.启动线程
thread.start();
// 再创建一个线程
Callable<String> callable1 = new MyCallable(200);
FutureTask<String> futureTask1 = new FutureTask<>(callable1);
Thread thread1 = new Thread(futureTask1);
thread1.start();
// 7.获取返回结果
try {
// 会等待子线程执行完毕后 才会提取结果
String result = futureTask.get();
System.out.println("第一个线程执行的结果:" + result);
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
String result1 = futureTask1.get();
System.out.println("第二个线程执行的结果:" + result1);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* 1.定义一个任务类 实现Callable借口 应该申明线程任务执行完毕后的结果的数据类型
*/
class MyCallable implements Callable<String> {
private int number;
public MyCallable(int number) {
this.number = number;
}
/**
* 2.重写call方法
*
* @return
* @throws Exception
*/
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= number; i++) {
sum += i;
}
return "子线程执行的结果是:" + sum;
}
}
方式三优点和缺点:
方式 | 优点 | 缺点 |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法。 | 扩展性较差,不能再继承其他类,不能返回线程执行的结果。 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。 | 编程相对复杂,不能返回线程执行的结果。 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果。 | 编程相对复杂。 |
Thread常用API说明:
当有很多线程在执行的时候,我们要怎么去区分这些线程?
Thread获取和设置线程名称:
方法名称 | 说明 |
---|---|
String getName() | 获取当前线程的名称,默认线程名称是Thread-索引。 |
void setName(String name) | 将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称。 |
Test.java
/**
* 目标:线程的API
*/
public class Test {
/**
* main方法主要是由主线程负责调度
*/
public static void main(String[] args) {
// 创建MyThread对象
Thread thread = new MyThread();
// 设置线程名称
thread.setName("1号");
// 启动子线程
thread.start();
Thread thread1 = new MyThread();
thread.setName("2号");
thread1.start();
// 获取当前线程对象
// 主线程名称叫做main
Thread threadMain = Thread.currentThread();
for (int i = 0; i < 10; i++) {
System.out.println(threadMain.getName() + "输出:" + i);
}
}
}
/**
* 自定义线程对象
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "输出:" + i);
}
}
}
Thread的构造器:
方法名称 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称。 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象。 |
public Thread(Runnable target, String name) | 封装Runable对象成为线程对象,并指定线程名称。 |
Test.java
/**
* 目标:线程的API
*/
public class Test {
/**
* main方法主要是由主线程负责调度
*/
public static void main(String[] args) {
// 创建MyThread对象
Thread thread = new MyThread("1号");
// 启动子线程
thread.start();
Thread thread1 = new MyThread("2号");
thread1.start();
// 获取当前线程对象
// 主线程名称叫做main
Thread threadMain = Thread.currentThread();
// threadMain.setName("主线程");
for (int i = 0; i < 10; i++) {
System.out.println(threadMain.getName() + "输出:" + i);
}
}
}
/**
* 自定义线程对象
*/
class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
// 为当前线程对象设置名称 传送至父类的有参数构造器初始化名称
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "输出:" + i);
}
}
}
Thread类的线程休眠方法:
方法名称 | 说明 |
---|---|
public static void sleep(long time) | 让当前线程休眠指定的时间后再继续执行,单位为毫秒。 |
Test.java
public class Test {
public static void main(String[] args) throws Exception {
for (int i = 0; i <= 5; i++) {
System.out.println("输出:" + i);
if (i == 3) {
// 休眠3秒
Thread.sleep(3000);
}
}
}
}
总结:
Thread常用方法、构造器。
方法名称 | 说明 |
---|---|
String getName() | 获取当前线程的名称,默认线程名称是Thread-索引。 |
void setName(String name) | 设置线程名称。 |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 |
public static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒。 |
public void run() | 线程任务方法。 |
public void start() | 线程启动方法。 |
Thread常用方法、构造器。
构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称。 |
public Thread(Runable target) | 把Runnable对象交给线程对象。 |
public Thread(Runnable target, String name) | 把Runnable对象交给线程对象,并指定线程名称。 |
线程安全问题:
取钱模型演示:
总结:
需求:
分析:
Account.java
public class Account {
private String cardId;
private double 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 Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
/**
* 取钱
* @param money 取出金额
*/
public void drawMoney(double money) {
// 查看是谁来取钱
String name = Thread.currentThread().getName();
// 1.判断金额是否足够
if (this.money >= money) {
// 2.取钱
System.out.println(name + "取钱成功,取出:" + money);
// 3.更新余额
// 此处将更新余额操作放在第三步而不是第二步 是为了模拟出现线程安全的场景
this.money = this.money - money;
System.out.println(name + "取钱后剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足!");
}
}
}
DrawThread.java
/**
* 取钱线程类
*/
public class DrawThread extends Thread {
// 账户对象
private final Account account;
public DrawThread(Account account, String threadName) {
// 线程名称
super(threadName);
this.account = account;
}
@Override
public void run() {
// 取钱
account.drawMoney(100000);
}
}
Test.java
public class Test {
public static void main(String[] args) {
// 1.定义线程类 创建一个共享账户对象
Account account = new Account("BOC-7979", 100000);
// 2.创建2个线程对象
new DrawThread(account, "小明").start();
new DrawThread(account, "小红").start();
}
}
小红取钱成功,取出:100000.0
小红取钱后剩余:0.0
小明取钱成功,取出:100000.0
小明取钱后剩余:-100000.0
总结:
线程同步:
线程同步的核心思想:
同步代码块:
synchronized (同步锁对象) {
// 操作共享资源的代码(核心代码)
}
锁对象要求:
锁对象用任意唯一的对象好不好?
锁对象的规范要求:
Account.java
public class Account {
private String cardId;
private double 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;
}
/**
* 静态方法使用 类名.class 作为锁对象
*/
public static void run() {
synchronized (Account.class) {
}
}
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
/**
* 取钱
* @param money 取出金额
*/
public void drawMoney(double money) {
// 查看是谁来取钱
String name = Thread.currentThread().getName();
// 同步代码快
// 此处名字无意义 字符串为常量池中唯一对象
// synchronized ("银行卡") {
// this = account
synchronized (this) {
// 1.判断金额是否足够
if (this.money >= money) {
// 2.取钱
System.out.println(name + "取钱成功,取出:" + money);
// 3.更新余额
// 此处将更新余额操作放在第三步而不是第二步 是为了模拟出现线程安全的场景
this.money = this.money - money;
System.out.println(name + "取钱后剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足!");
}
}
}
}
DrawThread.java
/**
* 取钱线程类
*/
public class DrawThread extends Thread {
// 账户对象
private final Account account;
public DrawThread(Account account, String threadName) {
// 线程名称
super(threadName);
this.account = account;
}
@Override
public void run() {
// 取钱
account.drawMoney(100000);
}
}
Test.java
public class Test {
public static void main(String[] args) {
// 1.定义线程类 创建一个共享账户对象
Account account = new Account("BOC-7979", 100000);
// 2.创建2个线程对象
new DrawThread(account, "小明").start();
new DrawThread(account, "小红").start();
}
}
小明取钱成功,取出:100000.0
小明取钱后剩余:0.0
小红来取钱,余额不足!
总结:
同步方法:
格式:
修饰符 synchronized 返回值类型 方法名称(形参列表) {
// 操作共享资源的代码
}
/**
* 取钱
* @param money 取出金额
*/
public synchronized void drawMoney(double money) {
// 查看是谁来取钱
String name = Thread.currentThread().getName();
// 1.判断金额是否足够
if (this.money >= money) {
// 2.取钱
System.out.println(name + "取钱成功,取出:" + money);
// 3.更新余额
// 此处将更新余额操作放在第三步而不是第二步 是为了模拟出现线程安全的场景
this.money = this.money - money;
System.out.println(name + "取钱后剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足!");
}
}
同步方法底层原理:
this
作为的锁对象,但是代码要高度面向对象。类名.class
作为的锁对象。总结:
Lock锁:
方法名称 | 说明 |
---|---|
public ReentrantLock() | 获得Lock锁的实现类对象。 |
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock();
}
}
}
Lock的API:
方法名称 | 说明 |
---|---|
void lock() | 获得锁。 |
void unlock() | 释放锁。 |
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String cardId;
private double money;
// final修饰后 锁对象是唯一和不可替换的 非常专业
private final Lock lock = new ReentrantLock();
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;
}
/**
* 静态方法使用 类名.class 作为锁对象
*/
public static void run() {
synchronized (Account.class) {
}
}
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
/**
* 取钱
* @param money 取出金额
*/
public void drawMoney(double money) {
// 查看是谁来取钱
String name = Thread.currentThread().getName();
// 1.判断金额是否足够
// 上锁
lock.lock();
try {
if (this.money >= money) {
// 2.取钱
System.out.println(name + "取钱成功,取出:" + money);
// 3.更新余额
// 此处将更新余额操作放在第三步而不是第二步 是为了模拟出现线程安全的场景
this.money = this.money - money;
System.out.println(name + "取钱后剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足!");
}
} finally {
// 解锁
lock.unlock();
}
}
}
什么是线程通向、如何实现?
线程通信常见模型:
Object类的等待和唤醒方法:
方法名称 | 说明 |
---|---|
void wait() | 让当前线程等待并释放所占所,直到另一个线程调用notify()方法或notifyAll()方法。 |
void notify() | 唤醒正在等待的单个线程。 |
void notifyAll() | 唤醒正在等待的所有线程。 |
注意:
线程通信案例模拟:
假如有这样一个场景,甲乙丙负责存钱,小明和小红负责取钱,必须一存、一取。
Account.java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String cardId;
private double 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 Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
/**
* 取钱
*
* @param money 取钱金额
*/
public synchronized void drawMoney(int money) {
String name = Thread.currentThread().getName();
try {
if (this.money >= money) {
// 钱足够 可取
this.money -= money;
System.out.println(name + "取钱" + money + "成功,余额为:" + this.money);
// 因为一存一取 没有钱了
this.notifyAll();
this.wait();
} else {
// 钱不够 不可取 唤醒存钱线程 等待自己
// 唤醒所有线程
this.notifyAll();
// 锁对象 让当前线程进入等待
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 存钱
* @param money 存入金额
*/
public synchronized void deposit(int money) {
String name = Thread.currentThread().getName();
try {
if (this.money == 0) {
// 如果账户余额为0 存入
this.money += money;
System.out.println(name + "存钱" + money + "成功,余额为:" + this.money);
this.notifyAll();
this.wait();
} else {
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
DrawThread.java
/**
* 取钱线程类
*/
public class DrawThread extends Thread {
// 账户对象
private final Account account;
public DrawThread(Account account, String threadName) {
// 线程名称
super(threadName);
this.account = account;
}
@Override
public void run() {
// 取钱
while (true) {
account.drawMoney(10000);
try {
// 休眠3秒
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
DepositThread.java
public class DepositThread extends Thread {
private Account account;
public DepositThread(Account account, String name) {
super(name);
this.account = account;
}
@Override
public void run() {
// 存钱
while (true) {
account.deposit(10000);
try {
// 休眠3秒
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Test.java
/**
* 了解线程通信的流程
*/
public class Test {
public static void main(String[] args) {
// 甲乙丙存钱 小明小红取钱
// 模拟线程通信思想 一存10000 一取10000
// 1.创建账户对象 代表5个人共同操作的账户
Account account = new Account("BOC-7979", 0);
// 2.创建2个取钱线程 代表小明和小红
new DrawThread(account, "小明").start();
new DrawThread(account, "小红").start();
// 3.创建3个存钱线程 代表甲乙丙
new DepositThread(account, "甲").start();
new DepositThread(account, "乙").start();
new DepositThread(account, "丙").start();
}
}
执行结果:
甲存钱10000成功,余额为:10000.0
小红取钱10000成功,余额为:0.0
丙存钱10000成功,余额为:10000.0
小红取钱10000成功,余额为:0.0
丙存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小红取钱10000成功,余额为:0.0
丙存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小红取钱10000成功,余额为:0.0
丙存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小红取钱10000成功,余额为:0.0
乙存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
丙存钱10000成功,余额为:10000.0
小红取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
甲存钱10000成功,余额为:10000.0
小明取钱10000成功,余额为:0.0
...
什么是线程池?
不是使用线程池的问题:
谁代表线程池?
如何得到线程池对象:
方式一:使用ExecutorService的实体类ThreadPoolExecutor自创建一个线程池对象。
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
ThreadPoolExecutor构造器的参数说明:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
线程池常见面试题:
ThreadPoolExecutor创建线程池对象实例:
ExecutorService executorService = new ThreadPoolExecutor(
3,
5,
8,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(6),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
ExecutorService的常用方法:
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行Runnable任务。 |
Future submit(Callable task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务。 |
void shutdown() | 等任务执行完毕后关闭线程池。 |
List shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务。 |
新任务拒绝策略:
策略 | 详解 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。(默认策略) |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常,这是不推荐的做法。 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前任务加入队列中。 |
ThreadPoolExecutor.CallerRunsPolicy | 由主线程负责调用任务的run()方法从而绕过线程池直接执行。 |
Test.java
import java.util.concurrent.*;
public class Test {
public static void main(String[] args) {
/* 1.创建线程池对象 3个核心线程 5个最大线程 临时线程最大时间为8秒 任务队列长度为3 */
ExecutorService executorService = new ThreadPoolExecutor(
3,
5,
8,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
/* 2.给任务线程池处理 */
Runnable runnable = new MyRunnable();
// 三个核心线程 处理多个任务
executorService.execute(runnable);
executorService.execute(runnable);
executorService.execute(runnable);
executorService.execute(runnable);
executorService.execute(runnable);
executorService.execute(runnable);
// 线程数量 > 核心线程数 + 任务队列长度 开始创建临时线程
executorService.execute(runnable);
executorService.execute(runnable);
try {
// 核心线程和临时线程都在忙 任务队列已满 开始拒绝任务
executorService.execute(runnable);
} catch (Exception e) {
e.printStackTrace();
}
/* 4.关闭线程池 开发中一般不会使用 */
// 立即关闭 即使任务没有完成 会丢失任务
// executorService.shutdownNow();
// 等待全部任务完成之后关闭
executorService.shutdown();
}
}
/**
* 自定义线程类
*/
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "本任务与线程绑定,线程进入休眠~~~");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
java.util.concurrent.RejectedExecutionException: Task com.java.threadpooldemo.MyRunnable@2a84aee7 rejected from java.util.concurrent.ThreadPoolExecutor@a09ee92[Running, pool size = 5, active threads = 5, queued tasks = 3, 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.java.threadpooldemo.Test.main(Test.java:34)
pool-1-thread-4输出了:HelloWorld0
pool-1-thread-4输出了:HelloWorld1
pool-1-thread-4输出了:HelloWorld2
pool-1-thread-4输出了:HelloWorld3
pool-1-thread-4输出了:HelloWorld4
pool-1-thread-1输出了:HelloWorld0
pool-1-thread-5输出了:HelloWorld0
pool-1-thread-5输出了:HelloWorld1
pool-1-thread-5输出了:HelloWorld2
pool-1-thread-5输出了:HelloWorld3
pool-1-thread-5输出了:HelloWorld4
pool-1-thread-5本任务与线程绑定,线程进入休眠~~~
pool-1-thread-4本任务与线程绑定,线程进入休眠~~~
pool-1-thread-2输出了:HelloWorld0
pool-1-thread-3输出了:HelloWorld0
pool-1-thread-3输出了:HelloWorld1
pool-1-thread-3输出了:HelloWorld2
pool-1-thread-3输出了:HelloWorld3
pool-1-thread-3输出了:HelloWorld4
pool-1-thread-3本任务与线程绑定,线程进入休眠~~~
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.java.threadpooldemo.MyRunnable.run(Test.java:59)
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)
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.java.threadpooldemo.MyRunnable.run(Test.java:59)
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)
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.java.threadpooldemo.MyRunnable.run(Test.java:59)
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)
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.java.threadpooldemo.MyRunnable.run(Test.java:59)
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)
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.java.threadpooldemo.MyRunnable.run(Test.java:59)
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)
pool-1-thread-2输出了:HelloWorld1
pool-1-thread-2输出了:HelloWorld2
pool-1-thread-2输出了:HelloWorld3
pool-1-thread-2输出了:HelloWorld4
pool-1-thread-2本任务与线程绑定,线程进入休眠~~~
pool-1-thread-1输出了:HelloWorld1
pool-1-thread-1输出了:HelloWorld2
pool-1-thread-1输出了:HelloWorld3
pool-1-thread-1输出了:HelloWorld4
pool-1-thread-1本任务与线程绑定,线程进入休眠~~~
Process finished with exit code 0
ExecutorSerivce的常用方法:
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行Runnable任务。 |
Future submit(Callable task) | 执行Callable任务,返回未来任务对象获取线程结果。 |
void shutdown() | 等任务执行完毕后关闭线程池。 |
List shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务。 |
MyCallable.java
import java.util.concurrent.Callable;
/**
* 1.定义一个任务类 实现Callable借口 应该申明线程任务执行完毕后的结果的数据类型
*/
public class MyCallable implements Callable<String> {
private int number;
public MyCallable(int number) {
this.number = number;
}
/**
* 2.重写call方法
*
* @return
* @throws Exception
*/
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= number; i++) {
sum += i;
}
return Thread.currentThread().getName() + "计算1-" + number + "的和是:" + sum;
}
}
Test.java
import java.util.concurrent.*;
public class Test {
public static void main(String[] args) throws Exception {
/* 1.创建线程池对象 3个核心线程 5个最大线程 临时线程最大时间为8秒 任务队列长度为3 */
ExecutorService executorService = new ThreadPoolExecutor(
3,
5,
8,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
/* 2.给任务线程池处理 */
Future<String> future1 = executorService.submit(new MyCallable(100));
Future<String> future2 = executorService.submit(new MyCallable(200));
Future<String> future3 = executorService.submit(new MyCallable(300));
Future<String> future4 = executorService.submit(new MyCallable(400));
Future<String> future5 = executorService.submit(new MyCallable(500));
// 3.取出结果
System.out.println(future1.get());
System.out.println(future2.get());
System.out.println(future3.get());
System.out.println(future4.get());
System.out.println(future5.get());
}
}
执行结果:
pool-1-thread-1计算1-100的和是:5050
pool-1-thread-2计算1-200的和是:20100
pool-1-thread-3计算1-300的和是:45150
pool-1-thread-3计算1-400的和是:80200
pool-1-thread-3计算1-500的和是:125250
Executors得到线程池对象的常用方法:
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
public static ExecutorService newSingleThreadExecutor() | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。
Test.java
import java.util.concurrent.*;
/**
* 目标:使用Executors的工具方法直接得到一个线程池对象
*/
public class Test {
public static void main(String[] args) throws Exception {
/* 1.创建固定线程数据的线程池 */
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new MyRunnable());
executorService.execute(new MyRunnable());
executorService.execute(new MyRunnable());
// 已经没有多余线程来处理
executorService.execute(new MyRunnable());
}
}
/**
* 自定义线程类
*/
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "本任务与线程绑定,线程进入休眠~~~");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Executors使用可能存在的陷阱:
方法名称 | 存在问题 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误(java.lang.OutOfMemoryError)。 |
public static ExecutorService newSingleThreadExector() | |
public static ExecutorService newCachedThreadPool() | 创建的线程数量最大上限是Integer.MAX_VALUE,线程数量可能会随着任务1:1增长,也可能出现OOM错误(java.lang.OutOfMemoryError)。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) |
阿里巴巴Java开发手册中这样写道:
4.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
- FixedThreadpool 和 SingleThreadPool:
允许的谐求队列长度为 Integez.MAX_ VALUE,可能会堆积大量的请求,从而导致 OOM。- CachedThreadPool fl ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
定时器:
定时器的实现方式:
Timer定时器:
构造器 | 说明 |
---|---|
public Timer() | 创建Timer定时器对象。 |
方法 | 说明 |
---|---|
public void schedule(TimerTask task, long delay, long period) | 开启一个定时器,按照计划处理TimerTask任务。 |
Test.java
import java.util.Timer;
import java.util.TimerTask;
/**
* 目标:Timer定时器的使用和了解
*/
public class Test {
public static void main(String[] args) {
// 1.创建Timer定时器
// 定时器本身就是一个单线程
Timer timer = new Timer();
// 2.调用方法 处理定时任务
// 3秒后 开始执行 之后每过2秒启动一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行一次!");
}
}, 3000, 2000);
}
}
Timer定时器的特点和存在的问题:
Test.java
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 目标:Timer定时器的使用和了解
*/
public class Test {
public static void main(String[] args) {
// 1.创建Timer定时器
// 定时器本身就是一个单线程
Timer timer = new Timer();
// 2.调用方法 处理定时任务
// 3秒后 开始执行 之后每过2秒启动一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行一次A!" + new Date());
// 休眠5秒 影响其他任务执行
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行一次B!" + new Date());
// 异常使得Timer线程死掉 影响后续任务执行
// System.out.println(10 / 0);
}
}, 0, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行一次C!" + new Date());
}
}, 0, 2000);
}
}
ScheduledExecutorService定时器:
Executors的方法 | 说明 |
---|---|
public static ScheduleExecutorService newScheduledThreadPool(int corePoolSize) | 得到线程池对象。 |
ScheduleExecutorService方法 | 说明 |
---|---|
public ScheduleFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) | 周期调用方法。 |
ScheduledExecutorService的特点:
Test.java
import java.util.Date;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
// 1.创建ScheduledExecutorService线程池 做定时器
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
// 2.开启定时任务
scheduledExecutorService.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:A" + new Date());
// 休眠 不影响其他任务执行
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, 0, 2, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:B " + new Date());
// 出现异常 不影响其他任务执行
System.out.println(10 / 0);
}
}, 0, 2, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:C " + new Date());
}
}, 0, 2, TimeUnit.SECONDS);
}
}
并发与并行:
并发的理解:
并行的理解:
总结:
线程的状态:
Java线程的状态:
public static enum State {
// 1.创建
NEW,
// 2.可运行
RUNNABLE,
// 3.锁阻塞
BLOCKED,
// 4.无限等待
WAITING,
// 5.计时等待
TIMED_WAITING,
// 6.被终止
TERMINATED;
private State() {
}
}
线程的6种状态互相转换:
线程的6种状态总结:
线程状态 | 描述 | |
---|---|---|
NEW 新建 | 线程刚被创建,但是并未启动。 | 创建线程对象。 |
RUNNABLE 可运行 | 线程已经被调用了start()等待CPU调度。 | start()方法。 |
BLOCKED 锁阻塞 | 线程在执行的时候未竞争到锁对象,则该线程进入BLOCKED状态。 | 无法获得锁对象。 |
WAITING 无限等待 | 一个线程进入WAITING状态,另一个线程调用notify()或者notifyAll方法才能够唤醒。 | wait()方法 |
TIMED_WAITING 计时等待 | 同WAITING状态,有几个方法有超时参数,调用他们将进入TIMED_WAITING状态。带有超时参数的常用方法有Thread.sleep、Object.wait。 | sleep()方法 |
TERMINATED 被终止 | 因为run()方法正常退出而死亡,或者因为没有捕获的异常终止了run()方法而死亡。 | 代码运行完毕。 |