一、多线程

(一)创建多线程的三种方法

1、继承Thread类,重写run()方法

image-20260208202757116

注意:一定要调用线程对象的start方法,如果调用run方法,JAVA会把它看成一个普通的线程。

2、实现Runnable接口,重写run()方法

image-20260208203501795

举例:

image-20260208203540856

写法二、匿名内部类:

image-20260208203820420

写法三、lambda表达式

3、实现Callable接口

前两种方法主线程拿不到子线程的返回值

而第三种方法可以。

image-20260208205025176

image-20260208210950878

举例:

image-20260208210148393

(省略MyCallable的定义代码,其中还写了MyCallable的有参构造函数,这样就可以对其进行传参了。)

那么,为什么不直接把FutureTask的功能给Callable呢?为什么还要多加一层?

gemini的回答:

image-20260208210906687

总结

image-20260208211031961

(二)线程的常用方法

image-20260209115237680

(三)线程同步

1、同步代码块

image-20260209162201623

synchronized()括号中的对象相当于是信号量

使用规范

image-20260209162742015

2、同步方法

image-20260209163040656

举例

image-20260209163258855

同步代码块和同步方法的区别

  1. 同步代码块锁的范围更小,性能更好
  2. 同步方法的可读性更好,实现更简单

3、Lock锁

相比前两种更加灵活。

image-20260209164445327

1
2
3
4
5
// 创建锁对象
private final Lock lk = new ReetrantLock();
//加锁、释放锁
lk.lock();
lk.unlock();

注意

  1. 锁对象最好用final修饰,防止其他人修改
  2. 释放锁的代码最好写在finally代码块中,保证无论是否出错,最后都会释放锁(try{} finally{}

(四)线程池

为什么需要线程池?

如果没有线程池,用户每发出一个请求,后台就需要创建一个新的线程来处理。

创建新线程的开销很大,并且请求过多时,会产生大量的线程,严重影响性能。

1、创建线程池对象

1️⃣使用ExecutorService的实现类ThreadPoolExecutor

image-20260209171143766

image-20260209171108474

ExecutorService接口的常用方法

image-20260209174143309

执行Runnable任务

image-20260209174519419

执行Callable任务

image-20260209174504643

注意事项

1、什么时候创建临时线程?

核心线程都忙 + 任务队列已满 + 还可以创建临时线程

2、什么时候拒绝新任务?

核心线程和临时线程都忙 + 任务队列已满

任务拒绝策略

image-20260209172932762

2️⃣使用工具类Executors创建线程池对象(不建议使用)

image-20260209175507688

注意

image-20260209211840638