多线程基础笔记
- 学习视频地址:https://www.bilibili.com/video/BV1Z54y1j7JT?p=3&share_source=copy_web
一、创建线程的3种方式
- 1.继承 Thread 类
- 2.实现Runnable接口
- 3.实现 Callable 接口
-
二、获取线程相关信息
1. 获取当前正在执行的线程
- Thread 类的静态方法: currentThread( )
![image-20220705102011913](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053544.png)
![image-20220705102023795](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053545.png)
![image-20220705102113462](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053546.png)
- 底层 toString 打印内容
![image-20220705102131000](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053547.png)
2. 获取和设置线程的名称
- 获取名称—— getName
![image-20220705102242917](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053548.png)
- 设置名称——setName
![image-20220705102314602](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053549.png)
3. 获取和设置线程优先级
线程优先级范围:1~10
- 设置: setPriority
- 获取:getPriority
![image-20220705102440823](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053552.png)
![image-20220705102501916](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053553.png)
![image-20220705102532782](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053554.png)
![image-20220705102554899](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053555.png)
![image-20220705102612821](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053556.png)
三、操作线程的基本方法
1. 使当前正在执行的线程进入休眠状态
- Thread 的静态方法: sleep(long millis 毫秒数)
![image-20220705102712349](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053557.png)
![image-20220705102743042](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053558.png)
![image-20220705102808976](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053559.png)
![image-20220705102820944](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053560.png)
2. run 方法 和 start 方法的区别
-
位置
-
类型
-
作用
-
线程数量
-
调用次数:方法调用多次,是否会出现新的问题
3. 让线程优雅地停止
- Thread 类的 interrupt 方法
![image-20220705102936606](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053563.png)
- 1.用于停止正在运行的线程
- 2.如果停止休眠中的线程,则会抛出线程中断异常
使用
![image-20220705103020215](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207080029697.png)
![image-20220705103045672](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053564.png)
问题:线程没有停下来,程序也并未结束
- 该方法只是将线程标记为中断状态,实际并未中断
- 需要在 run 方法中通过 isInterrupted 方法主动去判断线程是否被中断(是否有中断标记)
![image-20220705103810919](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053566.png)
![image-20220705104216996](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053567.png)
![image-20220705104504343](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053568.png)
4. 使当前线程放弃执行权
Thread.yield( ) 方法
- 用于一些需要一定条件才执行的任务
- 未满足条件时,调度到了该线程就放弃执行权
1、编写打印任务和赋值任务
-
打印线程
-
-
赋值线程
-
2、 主线程
![image-20220705104709399](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053571.png)
3、比较代码段的不同
- 空循环较为浪费资源
- yield 可以主动放弃执行权,而非被动地被调度
5. 等待另一条线程死亡( join 方法)
Thread.join( ) :等待调用该方法的线程死亡
用于使多个线程按顺序执行
例子
- 线程1
-
- 线程2
-
- 线程3
-
- 执行
-
6. 守护线程(Daemon)
使 A 线程随着 B 线程的结束而结束——设置 A 线程 为 B 线程的守护进程
1、设置后台线程
![image-20220705105440519](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053577.png)
测试
![image-20220705105613262](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053578.png)
2、判断线程是否为后台线程
![image-20220705105641198](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053579.png)
3、主线程
- 由执行结果可知,守护线程确实随着主线程的结束而结束了,并未受死循环影响。
![image-20220705105700173](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053580.png)
7. 如何得知线程是否执行完
通过判断线程是否存活
![image-20220705105731239](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053581.png)
测试
![image-20220705105753974](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053582.png)
![image-20220705105833213](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053583.png)
8. 线程组
1、获取线程组
- thread.getThreadGroup( )
![image-20220705110127242](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053584.png)
![image-20220705110201790](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053585.png)
![image-20220705110306526](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053586.png)
2、创建线程组
![image-20220705110335720](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053587.png)
![image-20220705110343698](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053589.png)
3、设置线程组
![image-20220705110409523](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053590.png)
![image-20220705110511155](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053591.png)
4、线程组常用方法
![image-20220705110525097](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053592.png)
![image-20220705110542724](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053593.png)
四、线程同步:synchronized
1. synchronized 关键字
- 可用来给对象和方法或者代码块加锁
- 被synchronized 修饰的代码,,同一时刻最多只有一个线程执行这个方法/这段代码。
同步代码块
同步方法
静态同步方法
测试
测试1:无同步操作
![image-20220705110919830](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053597.png)
![image-20220705110938670](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053598.png)
出现问题:有重复票
![image-20220705110843861](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053599.png)
修改建议:
- 需要添加同步操作
- 但不要将整个 while 循环放入同步代码块中,否则会出现一个线程把票买完的情况
测试2:同步代码块_无双检
![image-20220705111032126](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053600.png)
问题:出现错票
![image-20220705110803312](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053601.png)
问题原因:只剩 1 个资源,但有多个线程同时判断到有资源可用
![image-20220705111153295](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053602.png)
测试3:同步代码块_有双检
修改:进入同步代码块后,再进行一次条件检测
![image-20220705111325164](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053603.png)
记住问题
- 重复票:无同步操作
- 错票:有同步操作,但没有同步操作内外双重判断
2. 同步锁
- 同步锁是为了保证每个线程都能正常执行原子不可更改操作,【同步监听对象/同步锁/同步监听器/互斥锁】的一个标记锁。
- 同一时刻,最多只有一个线程拿到同步锁,然后执行同步代码。
按锁的类型分类
- 对象类型
- 类类型
-
同步类型对应的锁类型
![image-20220705160511181](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053605.png)
3. 多个线程争夺同一把锁和不同锁的场景
![image-20220708072813039](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053606.png)
![image-20220705161811089](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053608.png)
结果说明:当线程 sleep 时,是不会释放锁的
![image-20220705161941809](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053609.png)
修改测试:Task.class → Main.class
![image-20220708073108968](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053610.png)
![image-20220705162026438](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053611.png)
总结
- 线程的 sleep 方法,不会释放锁
- 争夺同一把锁,线程会阻塞
4. 死锁
1、什么是死锁
- 死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
- 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。
- 两个或两个以上的线程争夺彼此的锁,造成阻塞,程序永远处于阻塞状态。
2、死锁产生的条件
- 资源互斥:一个资源只能同时被一个线程占有
- 请求与保存:线程持有其资源的情况下,能申请获取另一个资源
- 不剥夺:线程任务未完成时,不主动释放自己的资源
- 循环等待:两个以上线程,构成环
3、死锁示例
-
实现思路
- 两个或两个以上的线程
- 两个或两个以上的锁(资源)
- 两个或两个以上的线程去持有不同的锁
- 持有着不同锁的线程(即在锁住的代码内容中)去争夺对方的锁
-
-
-
然后程序在输出 A、B之后,进入死锁。
五、线程间通讯
1. 等待(wait)、唤醒机制(notify)
- 首先需要有一个锁对象:lock
- 然后某个线程用 synchronized ( lock ) 获得该锁后,可调用 lock.wait( ) 方法,此时发生
- 让出这个锁资源:lock
- 同时线程开始无限期等待(如果没设置等待时间)
- 等待 lock.notify() (如果只有1条与该锁相关的线程,则被唤醒)或 lock.notifyAll 被唤醒继续执行
1、等待 wait
![image-20220705164018578](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053614.png)
![image-20220705164125135](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053615.png)
2、唤醒单个线程 notify
![image-20220705164158185](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053616.png)
![image-20220705164233135](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053617.png)
- 如果有多个线程执行 task ,则 notify 只唤醒1个线程
3、唤醒所有线程 notifyAll
4、总结
2. wait 与 sleep 的区别
- 位置
- 是否需要当前线程拥有锁
- 是否支持手动唤醒?
- 是否支持自动唤醒?
- 是否支持中断?
- 是否释放锁?
- 线程状态
位置
- sleep:是 Thread 类的静态方法
-
- wait:是 Object 类的方法
-
总结
thread.getState( ) :获取线程状态
3. 线程间通讯(wait & notify 应用)
1、数据类
/**
* 数据
*/
public class Data {
/**
* 数据信息
*/
private String message;
/**
* 设置数据信息
*
* @param message 数据信息
*/
public void setMessage(String message){
// 设置数据信息
this.message = message;
}
/**
* 获取数据信息
*
* @return 数据信息
*/
public String getMessage() {
return message;
}
}
2、生产者类
package lab;
/**
* 生产者任务
*/
public class Producer implements Runnable {
/**
* 数据
*/
private Data data;
/**
* 计数器
*/
private int count = 0;
/**
* 构造方法
*
* @param data 数据
*/
public Producer(Data data) {
// 设置数据
this.data = data;
}
/**
* 将需要线程执行的任务写在run()方法中
*/
@Override
public void run() {
// 无限循环
while (true) {
try {
// 让当前线程睡1秒钟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 同步对象为data的同步代码块
synchronized (data) {
// 当数据信息不为空时,即还有数据信息未消费
while (data.getMessage() != null) {
try {
// 使当前生产者线程等待
data.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 数据信息
String message = "你好!" + count++;
// 设置数据信息
data.setMessage(message);
// 唤醒正在此对象监视器上等待的所有线程
data.notifyAll();
}
}
}
}
3、消费者类
package lab;
/**
* 消费者任务
*/
public class Consumer implements Runnable {
/**
* 数据
*/
private Data data;
/**
* 构造方法
*
* @param data 数据
*/
public Consumer(Data data) {
// 设置数据
this.data = data;
}
/**
* 将需要线程执行的任务写在run()方法中
*/
@Override
public void run() {
// 无限循环
while (true){
// 同步对象为data的同步代码块
synchronized (data) {
// 当数据信息为空时
while (data.getMessage() == null) {
try {
// 等待
data.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费数据信息
System.out.println("数据信息:" + data.getMessage());
// 将数据对象里面的数据信息设置为null
data.setMessage(null);
// 唤醒正在此对象监视器上等待的所有线程
data.notifyAll();
}
}
}
}
4、主线程测试
package main;
import lab.Consumer;
import lab.Data;
import lab.Producer;
public class Main {
/**
* 主入口
*
* @param args 参数列表
*/
public static void main(String[] args) {
// 数据
Data data = new Data();
// 创建生产者任务和消费者任务
Producer producer = new Producer(data);
Consumer consumer = new Consumer(data);
// 创建生产者线程和消费者线程
Thread producerThread1 = new Thread(producer);
Thread consumerThread1 = new Thread(consumer);
// 启动线程
producerThread1.start();
consumerThread1.start();
}
}
六、线程同步:Lock 锁
具备和synchronized一样的作用,可以实现线程同步,但比synchronized 更强大。
Lock接口定义了 6 个方法
常用实现类
使用
-
package main; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { /** * 主入口 * * @param args 参数列表 */ public static void main(String[] args) { // 创建显式锁对象 Lock lock = new ReentrantLock(); // 通过匿名内部类方式实现Runnable接口 Runnable runnable = new Runnable() { @Override public void run() { // 获取锁 lock.lock(); try { // 使当前线程睡1秒钟 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 输出语句 System.out.println(Thread.currentThread().getName() + " --- run"); } }; // 创建线程 Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); Thread thread3 = new Thread(runnable); // 启动线程 thread1.start(); thread2.start(); thread3.start(); } }
总结
七、锁比较
1. 非阻塞式获取锁
1、阻塞式获取锁
-
发现锁未释放,则一直等待
-
2、非阻塞式获取锁
- 发现锁未释放,则先去做其他事
-
使用格式
测试
package main;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Task implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
// 非阻塞式获取锁
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 使当前线程休眠1秒钟
Thread.sleep(1000);
// 输出当前线程名称
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
} else {
System.out.println("锁被占用,执行其他任务。");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package main;
public class Main {
public static void main(String[] args) {
// 创建任务
Task task = new Task();
// 创建线程
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
// 启动线程
thread1.start();
thread2.start();
}
}
3、尝试定时获取锁
![image-20220706161728486](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053631.png)
2. 中断等待锁的线程
可中断测试
拿到锁持有 3 秒钟
![image-20220706162338191](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053633.png)
thread0持有锁时,还未释放,主线程此时主动中断 thread1
![image-20220706162405941](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053634.png)
发生异常
- 注意:该异常是 thread1 获取到锁之后抛出的
- 即 3 s 后,线程 thread1 已经拿到了锁,但发现自己已经被中断了,所以抛出异常
lockInterruptibly
- 当一个线程获取了锁之后,是不会被interrupt()方法中断的。
- 因为调用 interrupt( ) 方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程,而进入阻塞状态前是需要判断获取锁的
- 因此当通过 lockInterruptibly( ) 方法获取某个锁时,如果不能获取到,则可通过抛出异常来响应中断。
- 对比 lock 方法,该方法操作性更强
![image-20220706162557809](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053638.png)
![image-20220706162612523](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053639.png)
3. Lock 锁的等待唤醒机制
-
通过 Condition 实现的
-
每次返回的都是新的 Condition 实例
![image-20220706162839904](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053640.png)
1、Condition类的方法
![image-20220706162906684](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053641.png)
方法对应
常用方法
2、测试
package main;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) {
// 创建显式锁
Lock lock = new ReentrantLock();
// 获取Condition对象
Condition condition = lock.newCondition();
// 创建线程
Thread thread = new Thread(){
@Override
public void run() {
try {
// 获取锁
lock.lock();
// 输出语句
System.out.println(Thread.currentThread().getName() + " --- 等待");
// await()方法造成当前线程在等待,直到它被唤醒,通常由被通知或中断。
condition.await();
// 输出语句
System.out.println(Thread.currentThread().getName() + " --- 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
};
// 启动线程
thread.start();
}
}
测试唤醒
- 主线程添加代码
- 注:需要主线程先获取到 Lock 显式锁
测试唤醒全部
- 主线程添加代码
3、生产者与消费者(Condition应用)
1、数据类
-
定义两个不同的 Condition
-
package lab; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 数据 */ public class Data { /** * 数据信息 */ private String message; /** * 计数器 */ private int count = 0; /** * 当数据信息被消费时为true,当数据信息被生产时为false。 */ private boolean empty = true; /** * 显式锁 */ private Lock lock = new ReentrantLock(true); /** * 设置数据信息的Condition */ private Condition setCondition = lock.newCondition(); /** * 获取数据信息的Condition */ private Condition getCondition = lock.newCondition(); /** * 设置数据信息 * * @param message 数据信息 * @throws InterruptedException 当线程被中断时产生此异常 */ public void setMessage(String message) throws InterruptedException { // 获取锁 lock.lock(); try { // 当数据信息不为空时,即还有数据信息未消费 while (!empty) { // 使当前生产者线程等待 setCondition.await(); } // 设置数据信息 this.message = message + Thread.currentThread().getName() + " --- " + count++; // 数据信息没有被消费 empty = false; try { // 让当前线程睡1秒钟 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 唤醒正在此对象监视器上等待的单个线程 getCondition.signal(); } finally { // 释放锁 lock.unlock(); } } /** * 获取数据信息 * * @return 数据信息 * @throws InterruptedException 当线程被中断时产生此异常 */ public String getMessage() throws InterruptedException { // 获取锁 lock.lock(); try { // 当数据信息为空时 while (empty) { // 使当前生产者线程等待 getCondition.await(); } // 数据信息已经被消费 empty = true; // 唤醒正在此对象监视器上等待的单个线程 setCondition.signal(); } finally { // 释放锁 lock.unlock(); } // 返回数据信息 return message; } }
2、生产者
package lab;
/**
* 生产者任务
*/
public class Producer implements Runnable {
/**
* 数据
*/
private Data data;
/**
* 构造方法
*
* @param data 数据
*/
public Producer(Data data) {
// 设置数据
this.data = data;
}
/**
* 将需要线程执行的任务写在run()方法中
*/
@Override
public void run() {
try {
// 无限循环
while (true) {
// 设置数据信息
data.setMessage("你好!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、消费者
package lab;
/**
* 消费者任务
*/
public class Consumer implements Runnable {
/**
* 数据
*/
private Data data;
/**
* 构造方法
*
* @param data 数据
*/
public Consumer(Data data) {
// 设置数据
this.data = data;
}
/**
* 将需要线程执行的任务写在run()方法中
*/
@Override
public void run() {
try {
// 无限循环
while (true){
// 消费数据信息
System.out.println("数据信息:" + data.getMessage());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试
package main;
import lab.Consumer;
import lab.Data;
import lab.Producer;
public class Main {
/**
* 主入口
*
* @param args 参数列表
*/
public static void main(String[] args) {
// 数据
Data data = new Data();
// 创建生产者任务和消费者任务
Producer producer = new Producer(data);
Consumer consumer = new Consumer(data);
// 创建生产者线程和消费者线程
Thread producerThread1 = new Thread(producer);
Thread consumerThread1 = new Thread(consumer);
// 启动线程
producerThread1.start();
consumerThread1.start();
}
}
利用await、signal、signalAll等待唤醒机制,让线程间友好协作完成任务,更高效地使用资源。
4. 可重入锁和不可重入锁
- (不)可:(不)可以
- 重入:重复进入同步作用域(使用同步锁)
- 锁:同步锁
![image-20220706164514198](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053648.png)
![image-20220706164523778](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053649.png)
测试 synchronized 可重入锁
![image-20220706164717891](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053650.png)
说明可重复使用持有的锁
![image-20220706164731309](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053651.png)
同步方法上的同步锁,也可重复使用(毕竟锁住了整个类)
![image-20220706164833356](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053652.png)
测试 lock 可重入锁
![image-20220706164932891](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053653.png)
测试不可重入锁
自定义不可重入锁的思路
- 实现 Lock 接口——着重实现 lock 和 unlock 方法即可
- 绑定已获取锁的线程
- 实现获取锁的方法
- 实现释放锁的方法
package lab;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 不可重入锁
*/
public class NotReentrantLock implements Lock {
/**
* 用于绑定线程的变量
*/
private Thread thread;
/**
* 获取锁
*/
@Override
public void lock() {
// 获取当前线程
Thread currentThread = Thread.currentThread();
// 当当前线程为已经拿到锁的线程时
while (currentThread == thread) {
// 同步对象为this的同步代码块
synchronized (this) {
try {
// 使当前线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 记录当前线程
thread = currentThread;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
/**
* 释放锁
*/
@Override
public void unlock() {
// 获取当前线程
Thread currentThread = Thread.currentThread();
// 当当前线程不为绑定线程时
while (currentThread != thread) {
// 同步对象为this的同步代码块
synchronized (this) {
try {
// 使当前线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 将绑定线程置空
thread = null;
// 同步对象为this的同步代码块
synchronized (this){
// 唤醒等待该锁的所有线程
notifyAll();
}
}
@Override
public Condition newCondition() {
return null;
}
}
5. 公平锁与非公平锁
线程获取锁的机会平等与否
- 非公平锁(synchronized)
- 非公平锁(ReentrantLock)——默认是非公平的 ( false )
- 公平锁(ReentrantLock)
- 判断锁是否公平(isFair)——只能判断 ReentrantLock 和 ReentranReadWriteLock
- reentrantLock.isFair( )
- reentranReadWriteLock.isFair()
![image-20220706165714999](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053655.png)
6. 读(共享)锁与写(独占)锁
![image-20220706170237671](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053656.png)
![image-20220706170309097](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053657.png)
1、读锁
![image-20220706170431521](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053658.png)
![image-20220706170513594](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053659.png)
线程启动后,几乎可以同时拿到读锁
2、写锁
![image-20220706170553240](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053660.png)
- 三条线程依次输出(独占的,一个用完再给下一个)
3、读锁(共享锁)的意义
- 能保证支持多个线程同时读取的时候,禁止修改
- 能保证同一时刻只有一个线程在修改
7. 读写锁实战高并发容器
1、高并发容器
-
键值对容器
-
锁
- 读锁——获取数据
- 写锁——存储数据
-
方法
- 获取数据
- 存储数据
- 获取所有键
- 清空容器
-
package lab; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 高并发容器 */ public class ConcurrentDictionary { /** * 数据容器 */ private Map<String, String> m = new TreeMap<>(); /** * 可重入读写锁 */ private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true); /** * 读锁 */ private ReentrantReadWriteLock.ReadLock r = rwl.readLock(); /** * 写锁 */ private ReentrantReadWriteLock.WriteLock w = rwl.writeLock(); /** * 添加数据并将旧值返回给调用者 * * @param key 键 * @param value 值 * @return 旧值 */ public String put(String key, String value) { // 获取写锁 w.lock(); try { // 添加数据并将旧值返回给调用者 return m.put(key, value); } finally { // 释放写锁 w.unlock(); } } /** * 获取数据 * * @param key 键 * @return 键所对应的值 */ public String get(String key) { // 获取读锁 r.lock(); try { // 返回键所对应的值 return m.get(key); } finally { // 释放读锁 r.unlock(); } } /** * 获取容器中所有的key值 * * @return 容器中所有的key值 */ public List<String> allKeys() { // 获取读锁 r.lock(); try { // 返回容器中所有的key值 return new ArrayList<>(m.keySet()); } finally { // 释放读锁 r.unlock(); } } /** * 清空容器 */ public void clear() { // 获取写锁 w.lock(); try { // 清空容器 m.clear(); } finally { // 释放写锁 w.unlock(); } } }
2、写任务
![image-20220706171325215](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053662.png)
3、读任务
![image-20220706171405415](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053663.png)
4、测试
![image-20220706171515802](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053664.png)
8. synchronized 和 Lock 的区别
1、类型
- synchronized 关键字
- Lock 接口
-
2、格式
3、释放锁的方式
![image-20220706171754430](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053668.png)
![image-20220706171820530](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053669.png)
4、别名
![image-20220706171842616](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053671.png)
5、连续释放锁的方式
- 多个锁嵌套之后,释放的方式
-
-
-
-
6、(非)公平锁
![image-20220706180852305](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053676.png)
-
synchronized 只有非公平锁
-
7、读写锁
![image-20220706181035934](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053678.png)
-
ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); //写锁 Lock writeLock = readWriteLock.writeLock(); //读锁 Lock readLock = readWriteLock.readLock();
8、总结
八、等待、唤醒工具类:LockSupport
1. LockSupport 引入
LockSupport
-
功能
- 使线程等待
- 唤醒当前等待中的线程
-
该工具类无法被实例化
-
![image-20220706181223204](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053681.png)
![image-20220706181249029](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053682.png)
1、方法分类
- 按功能分类
- 都是静态方法,可直接通过类名调用
-
2、等待 park ( )
![image-20220706181612472](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053685.png)
3、唤醒 unpark ( )
![image-20220706181709335](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053686.png)
2. LockSupport 实现互斥锁
思路:
- 锁标志——记录锁是否已经被其他线程使用
- 等待队列
- 处理线程中断——有线程不想等了,需要处理
实现:
- 先进先出互斥锁:FIFOMutex
-
- 上锁方法的实现:lock( )
- 互斥锁的实质:同一时刻只有1个线程能用该锁,所以需要一个等待队列辅助排队。
-
-
-
- Task类
-
- Main
-
九、读写锁扩展
1. 读写锁互斥情况
![image-20220706182712416](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053692.png)
2. 读写锁升级版:StampedLock
-
普通的读写锁,存在“线程饥饿”问题
-
-
会导致写线程饥饿的情况:
- 当线程 A 持有读锁读取数据时,线程 B 要获取写锁修改数据就只能到队列里排队。
- 此时又来了线程 C 读取数据,那么线程 C 就可以获取到读锁,而要执行写操作线程 B 就要等线程 C 释放读锁。
- 由于该场景下读操作的数量远远大于写操作的数量,此时可能会有很多线程来读取数据而获取到读锁,那么要获取写锁的线程 B 就只能一直等待下去,最终导致饥饿。
-
StampedLock是基于CLH锁原理实现的
- CLH是一种基于排队思想实现的自旋锁,可以保证FIFO(先进先出)的服务顺序,所以会避免写线程饥饿问题
- 其实就是其中实现了一个队列,每次不管是读锁也好写锁也好,未拿到锁就加入队列
- 然后每次解锁后,队列头存储的线程节点获取锁,从而避免饥饿。
-
StampedLock 常用方法
3. StampedLock 的可重入性
- 可重入性和普通的读写锁有区别
十、线程状态与线程周期
获取线程状态的方法
![]()
- 线程状态
- 线程周期
十一、ThreadLocal
1. ThreadLocal 线程本地变量
![image-20220707000906053](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053699.png)
![image-20220707001024450](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053700.png)
本质
- Thread内部有一个ThreadLocalMap的成员变量,里面存放着和这个线程绑定的数据。
- 虽然ThreadLocalMap叫map,但是并没有实现map接口,只是提供了几个和map接口一样的方法
- ThreadLocal作为ThreadLocalMap的key
- 所以说ThreadLocal本身并不存放数据,数据都在Thread对象中的ThreadLocalMap里
- 所以同一个 ThreadLocal,在不同线程Thread的ThreadLocalMap中作为 key ,其 value 可以不同,相对独立。
ThreadLocal的定位是什么?
-
Thread 类的 ThreadLocalMap 属性的 key 值
-
-
-
-
-
-
key就是 ThreadLocal
-
ThreadLocal与Thread 的关系是什么?
- ThreadLocal 与Thread是相互协作的关系。
- Thread负责存储数据,ThreadLocal负责操作数据。
简单测试
![image-20220707001707267](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053707.png)
- 输出为 null
![image-20220707001809350](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053708.png)
-
修改
-
删除—— remove 方法
-
2. 线程间如何共享 ThreadLocal
InheritableThreadLocal
- 继承自 ThreadLocal,方法一样
- 就当做是属性值一样来用
![image-20220707002039530](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053712.png)
- 并不是说,线程 B extends 自线程 A
- 而是,【线程 B】 需要在【线程 A】 的 run 方法中创建
- 然后即可共享线程 A 中的本地变量
测试
- 线程B
-
- 线程A
-
- Main
-
3. 线程池之间如何共享 ThreadLocal
TransmitableThreadLocal
- 继承自 InheritableThreadLocal
- 该类非 JDK 自带的,而是 :https://github.com/alibaba/transmittable-thread-local
-
ThreadLocal 线程池测试
-
task
-
提交到线程池的任务,执行时无法获取到 ThreadLocal
-
InheritableThreadLocal 也一样
-
说明: ThreadLocal 和 InheritableThreadLocal 无法再线程池间共享
-
TransmitableThreadLocal 测试
发现仍然不一致;
需要按照规定使用:
-
方法一:改变线程池任务提交方式
- 借助 TtlRunnable 类,绑定任务 task
-
-
方法二:改变线程池
- 创建线程池:
- ExecutorService executorService = Executors.newFixedThreadPool( 1 );
TtlExcutors
.getTtlExecutorService
( executorService );
- 正常提交任务即可
- 创建线程池:
总结
十二、多线程基础扩展
1. 内存可见性——volatile
专门解决内存可见性问题的关键字
- 内存可见性
理解内存可见性
![image-20220707004010493](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053726.png)
发现, thread 线程并未停下来。
原因:
volatile 解决该问题
修改代码:
实现内存可见性。
即使用 volatile 修饰的变量,会主动通知更新【对应线程的工作内存】
2. 原子性
一个或多个操作不可中断的,要么全部执行成功,要么全部执行失败。
对应的操作——原子操作——具备原子性的操作
i++ 是原子操作吗?
- 不是
判断过程:让多个线程执行 i++ ,看最后的值是否为累加即可
![image-20220707004628651](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053730.png)
发现,i++不是原子操作,其本质步骤分为:
每一步都有可能被其他线程干扰
- 1.从主内存读取变量i
- 2.执行i++
- 3.将变量i写入主内存
使用 volatile 无法解决原子性问题
添加 同步代码 可以解决
使用原子类解决
具有原子性的类
![image-20220707004931349](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053733.png)
![image-20220707004950062](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053734.png)
![image-20220707005003787](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053736.png)
总结
![image-20220707005056842](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053737.png)
3. CAS (比较并交换)技术
比较并交换技术(CAS技术):CAS:Compare And Swap
CAS操作包含三个操作数:内存值、预期原值、新值
如果内存位置的值与预期原值相匹配,那么将内存值更新为新值;否则什么也不做
- 和谁比较
- 和谁交换
CAS操作包含三个值
- 内存中
- 预期原值
- 新值
测试线程干扰
- 线程干扰
- 当线程未执行到变量值修改的语句时,对应的变量值先被其他线程修改
CAS 方法抗干扰
CAS的应用
- 原子类里大多都是使用 CAS 方法来保证
总结
![image-20220707005615347](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053741.png)
4. 原子类
-
分类
-
5. ABA 问题
CAS算法存在的ABA问题
- 假如一个值原来是A,现在变成了B,
- 后来又变成了A,
- 那么CAS检查时会发现它的值没有发生变化,
- 但是实际上却变化了
解决方法
- 打标记(version等)
- 原子类
修改
总结
- ABA问题:假如一个值原来是A,现在变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了
- AtomicMarkableReference是一个带修改标记的引用类型原子类
- AtomicStampedReference是一个带版本号的引用类型原子类
![image-20220707004931349](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053733.png)
![image-20220707004950062](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053734.png)
![image-20220707005003787](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053736.png)
总结
![image-20220707005056842](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053737.png)
3. CAS (比较并交换)技术
比较并交换技术(CAS技术):CAS:Compare And Swap
CAS操作包含三个操作数:内存值、预期原值、新值
如果内存位置的值与预期原值相匹配,那么将内存值更新为新值;否则什么也不做
- 和谁比较
- 和谁交换
CAS操作包含三个值
- 内存中
- 预期原值
- 新值
- [外链图片转存中…(img-LmiX1huB-1657340436259)]
测试线程干扰
- 线程干扰
- 当线程未执行到变量值修改的语句时,对应的变量值先被其他线程修改
CAS 方法抗干扰
[外链图片转存中…(img-cp5nN0uD-1657340436259)]
[外链图片转存中…(img-YTU9xp6Y-1657340436260)]
CAS的应用
- 原子类里大多都是使用 CAS 方法来保证
总结
![image-20220707005615347](https://cyh11820.oss-cn-hangzhou.aliyuncs.com/noteimgs/noteimgs-master/img/202207090053741.png)
4. 原子类
-
分类
- [外链图片转存中…(img-7fJfXpFz-1657340436261)]
-
[外链图片转存中…(img-O03n1ML3-1657340436261)]
5. ABA 问题
CAS算法存在的ABA问题
- 假如一个值原来是A,现在变成了B,
- 后来又变成了A,
- 那么CAS检查时会发现它的值没有发生变化,
- 但是实际上却变化了
解决方法
- 打标记(version等)
- 原子类
[外链图片转存中…(img-4aG1DCZM-1657340436262)]
[外链图片转存中…(img-VG9A8qWg-1657340436262)]
修改
[外链图片转存中…(img-JWrtHnOI-1657340436263)]
[外链图片转存中…(img-xRvNxT75-1657340436264)]
[外链图片转存中…(img-6DyzF1A5-1657340436264)]
总结
- ABA问题:假如一个值原来是A,现在变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了
- AtomicMarkableReference是一个带修改标记的引用类型原子类
- AtomicStampedReference是一个带版本号的引用类型原子类