初识多线程(2.0)

目录

[等待一个线程的结束 join](#等待一个线程的结束 join)

[休眠当前线程 sleep](#休眠当前线程 sleep)

线程状态

线程安全

产生原因

解决方法

synchronized关键字

[监视器锁 monitor lock](#监视器锁 monitor lock)

可重入锁实现

死锁

​编辑

​编辑

构成死锁条件

[volatile 关键字](#volatile 关键字)

内存可⻅性


等待一个线程的结束 join

join 可以 决定 多个线程执行的 先后顺序

在以上的代码中 main 线程中 在 等待 t 线程结束再执行

在main中调用join

所以main 要等待 t 执行完成才能继续执行 否则只能堵塞等待中。。。

为了避免要处于一直等待中 所以 join又提供带参数的版本

复制代码
t.join(mills : 3000, nanos : 500);

当执行时间超过了 3秒 join 于是就不再等待了 就继续向下执行

nanos(纳秒) 在一般的场景中是不考虑这个参数的

因为这样的单位的精确 很难进行

休眠当前线程 sleep

也是在先前所使用到的方法

但有一点值得注意的是:

因为线程的调度是不可控的,

所以,这个⽅法只能保证 实际休眠时间是⼤于等于参数设置的休眠时间的。

此外 它还有着 sleep(0) 的 特殊用法

表明这里的线程 放弃当前的资源 等待操作系统的重写调配

(将CPU的资源让出执行其他

Thread

1.线程创建

2.关键属性

3.终止线程

4.线程等待

5.获取线程引用

6.线程休眠

线程状态

NEW:安排了⼯作,还未开始⾏动、

new 了新的 Thread对象 但还未 start

RUNNABLE:可⼯作的.⼜可以分成正在 ⼯作中和 即将开始⼯作.

**BLOCKED:**这⼏个都表⽰排队等着其他事情

也是一种特殊的等待,由 锁🔒造成的阻塞

WAITING:这⼏个都表⽰排队等着其他事情

死等 没有超时时间的等待

TIMED_WAITING:这⼏个都表⽰排队等着其他事情

线程阻塞中 (阻塞的时间是有上线的)

而且 join(时间)也会进入到 TIMED_WAITING 状态内

TERMINATED:⼯作完成了

内核中的线程已经结束,但是thread的对象还存在

线程安全

线程是并发执行的,调度是随机的

所以不写出安全的代码 会引发多线程的安全问题

按照正常逻辑

先执行t1 结束后

执行t2

或者

先执行t2 结束后

执行t1**(这里都不会引发阻塞)**

这里打印的值都是 t1 t2 所执行完的

理应说值应该是10000 但是结果相差甚远

所以这里产生了 线程安全问题

产生原因

1) 随机调度 抢占式执行**(根本)**

2)多个线程同时修改一个变量

3)修改操作不是原子的

个操作不能被中途打断,要么全部执行完,要么完全不执行

Java 里很多代码看起来是一行,实际是多个步骤,多线程切换时会打断执行,导致结果错误。

例如先前的++操作

4)内存可见性

可⻅性指,⼀个线程对共享变量值的修改,能够及时地被其他线程看到

5)指令重排序

编译器 / CPU 为了优化效率,会指令重排(打乱执行顺序),单线程没事,但多线程下会导致逻辑错误。

解决方法

synchronized关键字

1) 互斥

synchronized会起到互斥效果

某个线程执⾏到某个对象的synchronized中时,其他线程如果也执⾏ 到同⼀个对象synchronized就会阻塞等待.

synchronized(锁的对象){

一些要保证线程安全的代码

}

synchronized 修饰普通方法 相当于给this加锁

public class SynchronizedDemo {

public synchronized void methond() {

}

}

synchronized 修饰静态方法 相当于类给对象加锁

public class SynchronizedDemo {

public synchronized static void method() {

}

}

监视器锁 monitor lock

JVM中采用的术语

使用锁的会报出一些异常

可以通过 监视器锁 可以看到这些报错信息

2)可重入

死锁是非常严重的bug 当执行到这里时会直接卡住

但是对于JAVA 的 synchronized 来说

synchronized 同步块对同⼀条线程来说是**可重⼊**的,不会出现⾃⼰把⾃⼰锁死的问题

因此没有上⾯的问题.

此外还有一种特殊情况

可重入锁原理:

锁对象内部保存,到那哪个线程 ,在后续的线程再次加锁中 线程对比是否和以前的加锁线程相同

可重入锁实现

1.在锁的内部记录当前锁是哪个线程持有的,后续加锁进行判断

2.通过计数器确定要真正的要解锁的对象

修改代码如上所示

死锁

1) 一把线程 连续加两次锁🔒

上述已经提及

2)两把线程 两把锁🔒 每个线程在第一把锁后尝试获取对方的锁

以上是一个很常见的产生死锁的场景

在不释放第一把锁的前提之下 执行第二把锁

sleep 执行让这里的实现确保成功

因为可能当T1都执行完了 T2都还没开始呢

3)N个线程 M把锁

经典的哲学家就餐问题(这里不过多赘述)

在极端情况之下 都使用左手中的筷子 就会触发死锁 就开始一起等待

构成死锁条件

互斥条件

资源一次只能由一个进程占用,其他进程必须等待该资源释放 阻塞等待 。

非抢占条件

已分配给进程的资源不能被其他进程强行夺取,必须由进程主动释放。

synchronized 天然满足以上两个条件

请求并保持条件

一个锁在不释放的前提之下,又开启了第二个锁

循环等待条件

A等B B等C C等A

约定好加锁顺序就可以破除循环

只要避免3 和 4的其中一种既可以避免死锁

Java 标准库中很多都是线程不安全的.这些类可能会涉及到多线程修改共享数据,⼜没有任何加锁措 施.

•ArrayList

• LinkedList

• HashMap

• TreeMap

• HashSet

• TreeSet

• StringBuilder

但是还有⼀些是线程安全的.使⽤了⼀些锁机制来控制.

• Vector(不推荐使⽤)

• HashTable(不推荐使⽤)

对于加锁的代码 有了锁相当于可能与代码会发生竞争产生冲突 ---》 产生了阻塞 导致执行的效率大打折扣

• ConcurrentHashMap

• StringBuffer

(都在关键方法加入了synchronized)

volatile 关键字

内存可⻅性

是造成线程安全的主要原因之一

这里一个简单例子说明

一个线程读取

一个线程使用

修改线程的值并没有读取到 并没有被读写的线程读写到

所以使用了 volatile 关键字 修饰变量

此时编译器不会将其优化成寄存器中

复制代码
private volatile static int flag = 0;

volatile 解决了内存可见性 问题

依旧待续未完中....

相关推荐
叼烟扛炮1 小时前
C++ 知识点19 匿名对象
开发语言·c++·算法·匿名对象
0xDevNull1 小时前
Java十道高频面试题(二)
java·开发语言
叼烟扛炮1 小时前
C++ 知识点23 类模板
开发语言·c++·算法·类模版
java1234_小锋1 小时前
Spring AI 2.0 开发Java Agent智能体 - 会话记忆(Chat Memory)
java·人工智能·spring
Sylvia33.1 小时前
世界杯数据链路解析:从球场传感器到终端推送的毫秒级架构
java·前端·python·架构
xlq223221 小时前
53.tcp socket
linux·服务器·开发语言·网络·网络协议·tcp/ip
Royzst1 小时前
Lambda 算法基础 集合概述
java·开发语言
SmallBambooCode1 小时前
【人工智能】【Python】离线环境下huggingface预训练权重导入流程
开发语言·人工智能·python
Yeh2020581 小时前
Mybatis笔记一
java·笔记·mybatis