多线程初阶【2】
发布日期:2022-04-11 08:52:54 浏览次数:4 分类:技术文章

本文共 2831 字,大约阅读时间需要 9 分钟。

目录

synchronized关键字(monitor lock)

synchronized使用示例

修饰普通方法

在这里插入图片描述

修饰代码块

在这里插入图片描述

修饰静态方法

在这里插入图片描述

synchronized的特性

互斥

刷新内存

可重入

直观来讲,同一个线程针对同一个锁,连续加锁两次,如果出现了死锁,就是不可重入,如果不是死锁,就是可重入的。

在这里插入图片描述

这种代码在实际开发中,很容易被写出来,如果代码真的死锁了,程序的bug就太多了。
实现JVM的大佬们把synchronized实现成了可重入锁,对于可重入锁,上述连续加锁的操作,不会导致死锁。
原因:
可重入锁的内部,会记录当前的锁被哪个线程占用的,同时也会记录一个“加锁次数”。线程A针对锁,第一次加锁的时候,显然能够加锁成功,锁内部就记录了当前占用着的是A,同时加锁次数为1,后续在A对锁进行加锁时,此时就不是真的加锁,而是单纯的吧计数给自增,加锁次数为2,后续在解锁的时候,先把计数进行 -1,当锁的计数减到0的时候,就真的解锁。

外层先加了一次锁,里层对同一个对象在加一次锁。
外层锁:进入方法,则开始加锁,这次能够加锁成功,当前锁是没有人占用的。
里层锁:进入代码块,开始加锁,这次加锁不能加锁成功,得等到外层锁释放了之后,里层锁才能加锁成功。
这就死锁了。

可重入锁的意义:降低了程序员的负担,提高了开发效率。但同时也带来了代价,程序中需要更高的开销(维护锁属于哪个线程,并且加减计数,降低了运行效率)

Java 标准库中的线程安全类

1.Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施.
ArrayList、LinkedList、HashMap、TreeMap、HashSet、TreeSet、StringBuilder
2.但是还有一些是线程安全的. 使用了一些锁机制来控制。
Vector (不推荐使用)、HashTable (不推荐使用)、ConcurrentHashMap、StringBuffer
3.还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的。String

死锁

死锁的其他场景

一个线程一把锁

连续锁两次

两个线程两把锁

N个线程M把锁

举例:哲学家就餐问题
在这里插入图片描述

死锁的必要条件

互斥使用

一个锁被一个线程占用了之后,其他线程就占用不了(锁的本质,保证原子性)

不可抢占

一个锁被一个线程占用了之后,其他的线程就不能把这个锁给抢走。

请求和保持

当一个线程占用了多把锁之后,除非显示的释放锁,否则这些锁始终都是被该线程持有的。

环路等待

等待关系成环了。A等B,B等C,C等A
如何避免出现环路等待?
只要约定好,针对多把锁的时候,有固定的顺序即可。所有的线程都遵循同样的规则顺序,就不会出现环路等待。

前3条是锁本身的特点,实际开发中要想避免死锁,需从第4 条为切入点。

实际开发中,很少出现这种一个线程需要锁里面在套锁的情况。如果使用场景,不得不进行嵌套,一定要先约定好加锁的顺序。

Volatile

禁止编译器优化,保证内存可见性
volatile只是保证可见性,不保证原子性。volatile只是处理一个线程读,一个线程写的情况,synchronized都能处理。

在这里插入图片描述
JMM (java Memory Modle)
JMM就是把上述的硬件结构,在Java中用专门的术语重新抽象封装了一遍。
在这里插入图片描述
在这里插入图片描述

wait 和 notify

由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。
wait 和 notify 都是Object 对象的方法
调用wait 方法的线程,就会进入阻塞
阻塞到其他线程通过notify来通知

public class Demo17 {

   public static void main(String[] args) throws InterruptedException {
   Object object=new Object();
System.out.println("wait 前");
object.wait();
System.out.println("wait 后");
}}

执行效果
在这里插入图片描述
原因:
wait内部会做三件事
1.先释放锁
2.等待其他线程通知
3.收到通知之后,重新获取锁,并继续往下执行
因此要想使用wait/notify就要搭配synchronized

public class Demo17 {

   public static void main(String[] args) throws InterruptedException {
   Object object=new Object();
synchronized (object){
   System.out.println("wait 前");
object.wait();
System.out.println("wait 后");
}
}}

执行效果
在这里插入图片描述

public class Demo18 {

   private static Object locker =new Object();
public static void main(String[] args) throws InterruptedException {
   Thread t1=new Thread(() ->{
   //进行wait
synchronized (locker){
   System.out.println("wait之前");
try {
   locker.wait();
} catch (InterruptedException e) {
   e.printStackTrace();
}
System.out.println("wait之后");
}
});
t1.start();
Thread.sleep(3000);
Thread t2=new Thread(() ->{
   //进行notify
synchronized (locker){
   System.out.println("notify 之前");
locker.notify();
System.out.println("notify之后");
}
});
t2.start();
}}

执行效果
在这里插入图片描述

wait notify 都是针对同一个对象来操作的。
例如现在有一个对象o,有10个线程,都调用了o.wait,此时10个线程都是阻塞状态
如果调用了o.notify,就会把10个其中的一个给唤醒(唤醒哪一个,不确定)
针对notifyAll,就会把所有的10个线程都给唤醒
wait 唤醒之后,会重新尝试获取到锁(这个过程就会发生竞争)

转载地址:https://blog.csdn.net/m0_62824239/article/details/123756055 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:多线程到底是什么,带示例
下一篇:多线程初级入门学习

发表评论

最新留言

关注你微信了!
[***.11.154.118]2022年05月22日 09时35分12秒