目录
[等待一个线程的结束 join](#等待一个线程的结束 join)
[休眠当前线程 sleep](#休眠当前线程 sleep)
[监视器锁 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 解决了内存可见性 问题
依旧待续未完中....

