目录
什么是多线程?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
进程是程序的基本执行实体。
以前的线程是单线程程序,从头往下依次执行的,CPU不会切换到别的程序中去运行。多线程是CPU在多个线程中轮流切换,同时执行,把以前需要等待的时间利用起来,提高效率。
应用场景:软件中的耗时操作(如拷贝、迁移大文件、加载大量的资源文件)、所有的聊天软件、所有的后台服务器
总结:
- 什么是多线程? 有了多线程,我们就可以让程序同时做多件事情
- 多线程的作用? 提高效率
- 多线程的应用场景? 只要你想让多个事情同时运行就需要用到多线程,比如:软件中的耗时操作、所有的聊天软件、所有的服务器
多线程的实现方式
在Java中,实现多线程的方式主要有三种:继承Thread
类、实现Runnable
接口、实现Callable
接口。
一、继承Thread类
通过继承Thread
类并重写其run
方法,可以实现多线程。每个Thread
对象代表一个新的线程。
使用方法
- 创建一个类继承
Thread
类。 - 重写
Thread
类的run()
方法,把要执行的代码放在run()
方法中。 - 创建该类的实例,并调用
start()
方法启动线程。
示例代码:
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
适用场景
- 当不需要共享资源时,使用这种方式比较简单直接。
- 由于 Java 不支持多重继承,如果已经继承了另一个类,就不能再使用这种方式。
二、实现Runnable接口
通过实现Runnable
接口并重写其run
方法,可以将该接口的实现类作为线程任务传递给Thread
对象。
使用方法
- 创建一个类实现
Runnable
接口。 - 实现
Runnable
接口的run()
方法,把要执行的代码放在run()
方法中。 - 创建该类的实例,并将其传递给
Thread
类的构造函数,创建Thread
对象。 - 调用
Thread
对象的start()
方法启动线程。
示例代码:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread1.start();
thread2.start();
}
}
适用场景
- 当需要多个线程共享资源时,这种方式更加灵活。
- 实现接口的方式更适合需要从其他类继承的情况。
三、实现Callable接口
通过实现Callable
接口并重写其call
方法,可以创建有返回值的线程任务。与Runnable
不同的是,它可以返回结果,并且可以抛出异常。
使用方法
- 创建一个类实现
Callable
接口。 - 实现
Callable
接口的call()
方法,并在该方法中编写要执行的代码。 - 创建该类的实例,并将其传递给
FutureTask
类的构造函数,创建FutureTask
对象。 - 将
FutureTask
对象传递给Thread
类的构造函数,创建Thread
对象。 - 调用
Thread
对象的start()
方法启动线程。 - 调用
FutureTask
对象的get()
方法获取线程执行的结果。
示例代码:
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
public class CallableDemo {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
try {
Integer result = futureTask.get();
System.out.println("Sum: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
常见的成员方法
方法名称 | 说明 |
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public final void join() | 插入线程/插队线程 |
线程安全的问题
synchronized
关键字是 Java 中用于实现同步的机制,确保在同一时刻只能有一个线程访问被它保护的代码块或方法。它的主要作用是防止多个线程同时访问共享资源,避免出现数据不一致的问题。
synchronized
可以用在方法上,也可以用在代码块上。
当一个线程进入 synchronized
方法或代码块时,它会自动获得该方法或代码块的锁,其他线程在尝试进入时会被阻塞,直到该线程释放锁。锁的释放是在该线程退出 synchronized
方法或代码块时自动发生的。
注意事项
- 死锁:不当的锁使用可能会导致死锁,即两个或多个线程互相等待对方释放锁,导致程序无法继续执行。
- 性能问题:过度使用同步会导致性能下降,因为线程需要等待锁释放。
- 锁的范围:尽量缩小同步块的范围,只同步必要的代码部分,以减少线程争用,提高并发性。
同步代码块
通过同步代码块,可以确保同一时间只有一个线程可以执行被同步的代码,从而避免多个线程同时修改共享资源导致的数据不一致问题。
同步代码块的语法:
synchronized (lockObject) {
// 同步代码
}
lockObject
是一个对象,作为同步锁。所有要访问同步代码块的线程都必须先获得这个锁。
同步代码块中的代码是互斥执行的,即一个线程进入该代码块后,其他线程必须等待,直到该线程退出同步代码块。
同步代码块的锁对象
同步代码块的锁对象可以是任意对象,常见的选择包括:
this
:当前实例对象,用于同步实例方法中的代码块。ClassName.class
:类对象,用于同步静态方法或代码块。- 任意自定义对象:可以选择一个单独的对象作为锁,以便更细粒度地控制同步范围。
class SharedResource {
private static int resource = 0;
public void increment() {
synchronized (SharedResource.class) {
resource++;
System.out.println(Thread.currentThread().getName() + " incremented resource to " + resource);
}
}
}
同步方法
同步方法就是把synchronized关键字加到方法上
格式: 修饰符 synchronized 返回值类型 方法名(方法参数) {..},
如 private synchronized boolean method(){..}
特点
- 同步方法是锁住方法里面所有的代码
- 锁对象不能自己指定,非静态用this,静态用当前类的字节码文件对象
示例:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。
方法:先写同步代码块,然后把同步代码块在synchronized后面的方法,idea按住Ctrl+Alt+m,把后面的代码抽取成一个方法了。
ThreadDemo.java
MyRunnable mr =new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
MyRunnable.java
int ticket =0;
@override
public void run(){
//1.循环
while(true){
//2.同步代码块(同步方法)
if(method()) break;
}
}
//this
private synchronized boolean method(){
//3.判断共享数据是否到了末尾,如果到了末尾
if(ticket == 100){
return true;
}else {
//4.判断共享数据是否到了末尾,如果没有到末尾
try {
//如果不设置休眠10毫秒,一个线程在短时间内全部执行完,看不到多线程执行的效果了
Thread.sleep(10);
}catch(InterruptedException e){
e.printstackTrace();
}
ticket++;
System.out.println(Thread.currentThread().getName()
}
return false;
}
Lock锁
synchronized(obj)会自动打开自动关闭,没法自我控制。这里介绍一个锁对象Lock,Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
static int ticket =0;
static Lock lock = new ReentrantLock();
@override
public void run(){
while(true){
//synchronized(MyThread.class){
lock.lock();
if(ticket == 100){
Lock.unlock();
break;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e){
e.printstackTrace();
}
ticket++;
System.out.println(getName()+"在卖第"+ticket);
}
Lock.unlock();
}
}
把开锁XX.unlock()放在finally更好
static int ticket =0;
static Lock lock =new ReentrantLock();
@override
public void run(){
while(true){
lock.lock();
try {
if(ticket == 100){
break;
}else{
Thread.sleep(10);
ticket++;
System.out.println(getName()+"在卖第"+ticket);
}catch(InterruptedException e){
e.printstackTrace();
} finally {
Lock.unlock();
}
}
生产者和消费者
方法名称 | 说明 |
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
示例:厨师煮面条(线程1) 面条放在桌子上 吃货吃面条(线程2)
public class Desk{ //作用:控制生产者和消费者的执行
public static int foodFlag = 0; //是否有面条。0:没有面条,1:有面条。
public static int count = 10; //总个数
public static object lock = new object(); //锁对象
}
等待唤醒机制---消费者代码实现 Desk.java
while(true){
synchronized(Desk.lock){
if(Desk.count == 0){
break;
}else{
if(Desk.foodFlag ==0){ //先判断桌子上是否有面条,如果没有,就等待
try {
Desk.lock.wait(); //让当前线程跟锁
catch(InterruptedException e){
e.printstackTrace();
}else{
Desk.count--; //把吃的总数-1
System.out.println("吃货在吃面条,还剩"+Desk.count+"碗面条");
Desk.lock.notifyA11(); //吃完之后,唤醒厨师继续做
Desk.foodFlag =0; //修改桌子的状态
}
}
}
}
等待唤醒机制---生成者代码实现 Cook.java
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.notifyA11(); //叫醒等待的消费者开吃
}
}
}
}
}
}
使用wait()方法时不能直接使用wait(),要调用锁Desk.lock.wait();这样释放锁的时候就知道释放哪些线程。
测试类 ThreadDemo.java
public class ThreadDemo {
public static void main(string[] args){
Cook c = new cook(); //创建线程的对象
Foodie f = new Foodie();
c.setName("厨师"); //给线程设置名字
f.setName("吃货");
c.start(); //开启线程
f.start();
}
}
运行结果
阻塞队列实现等待唤醒机制
接口 | lterable、Collection、Queue、BlockingQueue |
实现类 | ArrayBlockingQueue:底层是数组,有界 LinkedBlockingQueue:底层是链表,无界但不是真正的无界最大为int的最大值 |
Cook.java
public class Cook extends Thread{
ArrayBlockingQueue<string> queue;
public Cook(ArrayBlockingQueue<string> queue){ this.queue=queue};
@override
public void run(){
while(true){
//不断的把面条放到阻塞队列当中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printstackTrace();
}
}
}
}
Foodie.java
public class Foodie extends Thread{
ArrayBlockingQueue<string> queue;
public Foodie(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();
}
}
}
}
测试类ThreadDemo.java
public class ThreadDemo {
public static void main(string[] args){
//创建阻塞队列的对象,后面的1是队列的大小,最多存放一个数据
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
Foodie f = new Foodie(queue); //2.创建线程的对象,并把阻塞队列传递过去
Cook c=new cook(queue);
c.start(); //3.开启线程
f.start();
}
}
线程的生命周期
线程的6种状态
状态 | 说明1 | 说明2 |
新建状态(NEW) | 创建线程对象 | 至今尚未启动的线程 |
就绪状态(RUNNABLE) | start方法 | 正在 Java 虚拟机中执行的线程 |
阻塞状态(BLOCKED) | 无法获得锁对象 | 受阻塞并终待某个监视器锁的线程 |
等待状态(WAITING) | wait方法 | 无限期地等待另一个线程来执行某一特定操作的线程 |
计时等待(TIMED WAITING) | sleep方法 | 等待另一个线程来执行取决于指定等待时间的操作的线程 |
结束状态(TERMINATED) | 全部代码运行完毕 | 已退出的线程 |
多线程练习
1.卖电影票
一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒
要求:请用多线程模拟卖票过程并打印剩余电影票的数量
public class TicketSeller implements Runnable {
private static int tickets = 1000;
@Override
public void run() {
while (true) {
boolean ticketSold = false;
synchronized (TicketSeller.class) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售出一张票, 剩余票数: " + (--tickets));
ticketSold = true;
} else {
break;
}
}
if (ticketSold) {
try {
Thread.sleep(3000); // 模拟每次领取时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
TicketSeller seller = new TicketSeller();
Thread window1 = new Thread(seller, "1号窗口");
Thread window2 = new Thread(seller, "2号窗口");
window1.start();
window2.start();
}
}
将 Thread.sleep(3000)
移出同步代码块,这样就可以让其他线程在一个线程等待时继续售票。
2.送礼品
有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出,利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
public class GiftSender implements Runnable {
private static int gifts = 100;
private static boolean isPerson1Turn = true;
@Override
public void run() {
while (true) {
synchronized (GiftSender.class) {
if (gifts < 10) {
// 唤醒所有等待的线程,以确保它们退出
GiftSender.class.notifyAll();
break;
}
// 等待轮到当前线程执行
while ((Thread.currentThread().getName().equals("玩家1") && !isPerson1Turn) ||
(Thread.currentThread().getName().equals("玩家2") && isPerson1Turn)) {
try {
GiftSender.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 再次检查礼品数量以确保正确
if (gifts >= 10) {
System.out.println(Thread.currentThread().getName() + " 送出一份礼品, 礼物的剩余数量为: " + (--gifts));
}
isPerson1Turn = !isPerson1Turn; // 切换当前线程
GiftSender.class.notifyAll(); // 唤醒所有等待的线程
}
}
}
public static void main(String[] args) {
GiftSender sender = new GiftSender();
Thread person1 = new Thread(sender, "玩家1");
Thread person2 = new Thread(sender, "玩家2");
person1.start();
person2.start();
}
}
两个线程交替进行,不然会让某个线程一直执行完才会释放synchronized锁。
3.打印奇数数字
同时开启两个线程,共同获取1-100之间的所有数字
要求:将输出所有的奇数。
public class OddNumberPrinter implements Runnable {
private static int number = 1;
@Override
public void run() {
while (number <= 100) {
synchronized (OddNumberPrinter.class) {
if (number % 2 != 0) {
System.out.println(Thread.currentThread().getName() + " printed: " + number);
number++;
OddNumberPrinter.class.notify(); // 唤醒其他等待的线程
try {
OddNumberPrinter.class.wait(); // 使当前线程等待,直到其他线程唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
number++;
}
}
}
synchronized (OddNumberPrinter.class) {
OddNumberPrinter.class.notifyAll(); // 确保所有线程在结束时被唤醒
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(new OddNumberPrinter(), "线程1");
Thread thread2 = new Thread(new OddNumberPrinter(), "线程2");
thread1.start();
thread2.start();
}
}
4.抢红包
抢红包也用到了多线程。
假设:100块,分成了3个包,现在有5个人去抢。其中,红包是共享数据5个人是5条线程
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
public class RedWars implements Runnable {
static double money = 100; // 红包总额
static int count = 3; // 抢红包人数
static final double MIN = 0.01; // 抢红包最小金额
@Override
public void run() {
synchronized (RedWars.class) {
if (count == 0) {
System.out.println(Thread.currentThread().getName() + "没有抢到红包");
} else {
double price = 0;
if (count == 1) {
price = money; // 最后一个人抢到剩下的金额
} else {
Random r = new Random();
double bounds = money - (count - 1) * MIN; // 最大红包金额限制
price = r.nextDouble() * bounds;
if (price < MIN) {
price = MIN;
}
}
// 使用 BigDecimal 保留两位小数
BigDecimal priceBigDecimal = new BigDecimal(price).setScale(2, BigDecimal.ROUND_HALF_UP);
price = priceBigDecimal.doubleValue();
money = money - price;
count--;
System.out.println(Thread.currentThread().getName() + "抢到了" + price + "元");
}
}
}
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
new Thread(new RedWars(), "抢红包玩家" + i + "号").start();
}
}
}
5.抽奖箱抽奖
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程),设置线程名称分别为“抽奖箱1"、“抽奖箱2”,随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖
......
public class LotteryBox implements Runnable {
private final ArrayList<Integer> list;
public LotteryBox(ArrayList<Integer> list) {this.list = list;}
@Override
public void run() {
while (true) {
synchronized (LotteryBox.class) {
if (list.size() == 0) {
break;
} else {
Collections.shuffle(list); //随机打乱给定列表list中元素的顺序。
int price = list.remove(0);
System.out.println(Thread.currentThread().getName() + " 产生了一个 " + price + " 元的大奖");
}
}
try {
Thread.sleep(500); // 模拟抽奖时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
Thread box1 = new Thread(new LotteryBox(list), "抽奖箱1");
Thread box2 = new Thread(new LotteryBox(list), "抽奖箱2");
box1.start();
box2.start();
}
}
6.多线程统计并求最大值
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程),设置线程名称分别为“抽奖箱1"、“抽奖箱2”,随机从抽奖池中获取奖项元素,每次抽的过程中,不打印,抽完时一次性打印(随机),例如:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项
分别为:10,20,100,500,2,300,最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
分别为:5,50,200,800,80,700,最高奖项为800元,总计额为1835元
public class LotteryBox implements Runnable {
private final ArrayList<Integer> list;
public LotteryBox(ArrayList<Integer> list) {this.list = list;}
@Override
public void run() {
//每个线程都有独立的boxList,使用了线程栈
ArrayList<Integer> boxList = new ArrayList<>();
while (true) {
synchronized (LotteryBox.class) {
if (list.size() == 0) {
System.out.println(Thread.currentThread().getName()+ boxList);
break;
} else {
Collections.shuffle(list);
int price = list.remove(0);
boxList.add(price);
}
}
try {
Thread.sleep(10); // 模拟抽奖时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
Thread box1 = new Thread(new LotteryBox(list), "抽奖箱1");
Thread box2 = new Thread(new LotteryBox(list), "抽奖箱2");
box1.start();
box2.start();
}
}
线程栈(Thread Stack)
线程栈是每个线程在内存中独立拥有的栈空间,用于存储线程在运行时的局部变量、方法调用信息和一些临时数据。
线程栈的特点:
- 每个线程都有自己的栈空间,彼此独立,互不干扰。
- 线程栈的生命周期与线程相同,当线程结束时,线程栈也会被释放。
- 在多线程环境中,每个线程都有自己的线程栈。这意味着各个线程之间的局部变量和方法调用是相互独立的,不会互相影响。
7.多线程之间的比较
在上一题基础上继续完成如下需求:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300,最高奖项为300元,总计额为932元。
在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5,50,200,800,80,700,最高奖项为800元,总计额为1835元。
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元。(比上一题多了这一句话)
提示:因为要比较两个线程中谁获得了最大奖项,可以比较两个数组中的最大值。这里刚好可以用实现Callable的方法,返回线程的值。
public class MyCallable implements Callable<Integer> {
private final ArrayList<Integer> list;
public MyCallable(ArrayList<Integer> list) {this.list = list;}
@Override
public Integer call() throws Exception {
ArrayList<Integer> boxList = new ArrayList<>(); //每个线程都有独立的boxList,使用了线程栈
while (true) {
synchronized (LotteryBox.class) {
if (list.size() == 0) {
System.out.println(Thread.currentThread().getName() + boxList);
break;
} else {
Collections.shuffle(list);
int price = list.remove(0);
boxList.add(price);
}
}
Thread.sleep(10);
}
if(boxList.size()== 0){
return null;
}else {
return Collections.max(boxList);
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
//创建多线程要运行的参数对象
MyCallable mc =new MyCallable(list);
//创建多线程运行结果的管理者对象
FutureTask<Integer> Box1= new FutureTask<>(mc);
FutureTask<Integer> Box2= new FutureTask<>(mc);
//创建线程对象
Thread box1 = new Thread(Box1);
Thread box2 = new Thread(Box2);
box1.setName("抽奖箱1");
box2.setName("抽奖箱2");
box1.start();
box2.start();
Integer maxbox1 = Box1.get();
Integer maxbox2 = Box2.get();
String winningBox = maxbox1 > maxbox2? "抽奖箱1" : "抽奖箱2";
System.out.println("在此次抽奖过程中," + winningBox + "中产生了最大奖项,该奖项金额为" + Math.max(maxbox1, maxbox2) + "元。");
}
}
线程池
线程池是一种预先创建并管理多个线程的技术,用于提高应用程序的性能和资源利用率。通过使用线程池,应用程序可以避免频繁创建和销毁线程的开销,减少资源消耗,提高响应速度。
线程池代码实现:1.创建线程池 2.提交任务 3.所有的任务全部执行完毕,关闭线程池(可以不关)
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建一个没有上限的线程池 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建一个固定大小的线程池 |
线程池实现还包括:
ScheduledThreadPool:支持定时和周期性任务的线程池。
SingleThreadExecutor:单线程的线程池,所有任务在同一线程中按顺序执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService pool1 = Executors.newFixedThreadPool(3);
// 提交任务给线程池执行
for (int i = 0; i < 10; i++) {
pool1.execute(() -> {
// 线程执行的具体逻辑
System.out.println(Thread.currentThread().getName() + " 正在执行任务");
});
}
// 关闭线程池
pool1.shutdown();
}
}
运行的其一结果:
pool-1-thread-2 正在执行任务
pool-1-thread-3 正在执行任务
pool-1-thread-1 正在执行任务
pool-1-thread-3 正在执行任务
pool-1-thread-1 正在执行任务
pool-1-thread-2 正在执行任务
pool-1-thread-3 正在执行任务
pool-1-thread-2 正在执行任务
pool-1-thread-1 正在执行任务
pool-1-thread-3 正在执行任务
自定义线程池
可以使用 ThreadPoolExecutor
类来创建自定义线程池,通过构造函数的参数来精细控制线程池的行为:
import java.util.concurrent.*;
public class CustomThreadPoolDemo {
public static void main(String[] args) {
// 创建自定义线程池
ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60, // 线程空闲时间
TimeUnit.SECONDS, // 线程空闲时间单位
new LinkedBlockingQueue<>(10), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 提交任务给线程池执行
for (int i = 1; i <= 15; i++) {
int taskNumber = i;
customThreadPool.submit(() -> {
System.out.println("任务" + taskNumber + "开始执行");
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务" + taskNumber + "执行完毕");
});
}
// 关闭线程池
customThreadPool.shutdown();
}
}
ThreadPoolExecutor
类的构造函数的参数核心有7个:线程数量、最大线程数、空闲时间值、空闲时间单位、阻塞队列、创建线程的方式、拒绝策略
举个通俗易懂的例子:假设一个餐厅一名服务员只为一个客户服务,服务完一位客户才能继续服务下一位客户。这个餐厅正式服务员(参数1:核心线程数量)有三名,临时服务员有三名,餐厅最多容纳六名服务员(参数2:线程池中最大线程的数量)。如果在某一段时间内(参数3、4:空闲时间值、单位),餐厅没什么生意,就会把临时员工辞退。生意好的时间段,餐厅坐不下,会有排队的客户(参数5:阻塞队列),到时候老板就要招人(参数6:线程工厂)。餐厅门口最多排十名客户,当排队人数多于10位时,超出顾客拒绝服务请下次再来(参数7:拒绝策略)。正式员工是不会被辞退的,而临时员工超出餐厅空闲时间就会被辞退。