JAVAEE初阶相关内容第五弹--多线程(初阶)

目录

JAVA标准库中的线程安全

※※死锁

几种关于死锁的情况

1.一个线程一把锁

2.两个线程两把锁

3.多个线程多把锁

哲学家就餐--死锁问题

死锁形成的四个必要的条件

(1)互斥作用

(2)不可抢占

(3)请求和保持

(4)循环等待

如何避免死锁呢?


JAVA标准库中的线程安全

java标准库中有很多都是线程不安全的,这些类可能会涉及到多线程修改共享数据,又没有任何的加锁措施。如果多线程操作同一个集合类,就需要考虑到线程安全的问题。

这些类在多线程代码中要格外注意,不安全!
ArrayList
LinkedList
HashMap
TreeMap
HashSet
TreeSet
Stringbuilder

既然不安全为什么不全都加锁?

加锁这个操作是有负面影响的,如果对于没有线程安全的情况下,可以放心使用;如果有线程安全的问题时可以手动的进行加锁,这样给了我们更多的选择空间,如果都加上锁之后,就会产生额外的时间开销。

但是还有一些是线程安全的,使用了一些锁机制来控制
Vector不推荐使用
HashTable不推荐使用
ConcurrentHashMap
StringBuffer
还有虽然没加锁,但是不涉及到“修改·”,仍然是线程安全的
String

※※死锁

死锁问题是一个十分影响我们幸福感的事情,一旦我们的程序出现了死锁,就会导致线程崩掉,无法继续执行后续的工作,程序就有很严重的bug,而且死锁是非常隐蔽的,在开发阶段不经意间就会写出死锁代码,不容易被测试出来。

几种关于死锁的情况

1.一个线程一把锁

连续加锁两次,如果锁是不可重入锁,就会死锁。

2.两个线程两把锁

t1和t2各自先针对A锁和B锁进行加锁,再尝试获取对方的锁,就会产生死锁现象。

【故事:假设S和Z两个人去吃饺子,S拿到了醋,Z拿到了酱油;这时S想拿Z的酱油,Z想拿S的醋,在这种情况下两个人都想等对方释放锁,互不相让就会产生死锁。】

代码实现:

public class ThreadD14 {
    public static void main(String[] args) {
        Object jiangyou =new Object();
        Object cu = new Object();

        //建立两个线程分别表示S和Z两个人
        Thread S = new Thread(() -> {
            synchronized (cu){
                //这里让程序休眠确保S可以顺利拿到cu
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (jiangyou){
                    //这里打印一下看程序是否可以顺利的执行到此处
                    System.out.println("S顺利的拿到了酱油&醋");
                }
            }


        });
        Thread Z = new Thread(() -> {
            synchronized (jiangyou){
                //这里同理让程序休眠,使Z顺利拿到酱油
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (cu){
                    System.out.println("Z顺利的拿到了酱油&醋");

                }
            }
        });
        //让线程运行
        S.start();
        Z.start();
    }
}

我们开始运行后发现:

可以看出程序进入了死锁状态。

现在有一个人,把房间的钥匙锁在了车里,而车钥匙锁在了房间里,这种例子也就是死锁状态。

针对这样的死锁问题,就需要我们借助像jconsole这样的工具进行定位,看线程和状态的调用栈,就可以分析出来代码是在哪里产生的死锁问题。

3.多个线程多把锁

多个线程多把锁相当于两个线程两把锁的一般情况。

哲学家就餐--死锁问题

教科书上的经典案例:哲学家就餐问题。

有五个哲学家和五根筷子,每个哲学家存在两种状态

状态一:思考人生(相当于线程阻塞)

状态二:拿起手套吃西瓜(这里假设必须两只手套都拿着才能吃西瓜,硬性假设)

由于操作系统的随即调度,这五个哲学家随时都有可能想吃西瓜,也随时有可能要思考人生。

假设出现了极端的情况,就会产生死锁。在同一时刻,所有的哲学家都同时拿起了左手的手套,都在等待着右边的哲学家把手套放下。

锁更多,线程就更多,情况就更复杂。

下面我们来分析一下死锁是怎么形成的。

死锁形成的四个必要的条件

同时具备,才能出现死锁!!

(1)互斥作用

线程1拿到了锁,线程2就需要等待

(2)不可抢占

线程1拿到锁后,必须是线程1主动的释放,不能说是线程2强行的获取到。

(3)请求和保持

线程1拿到锁A后,再尝试想获取锁B,A的这把锁还是保持的(不会因为获取锁B就把A给释放了)

(4)循环等待

线程1尝试获取锁A和锁B,线程2尝试获取锁B和锁A,线程1在获取B的时候等待线程2释放B。同时线程2在获取A的时候等待线程1释放A。

说是四个条件,实际上就是一个条件,前三个条件都是锁的基本特性,对于synchronized来说我们也不能修改。

如何避免死锁呢?

打破必要条件即可,突破口就是循环等待。

办法:给锁进行编号,然后指定一个固定的顺序(比如从小到大)来加锁。

任意线程加多把锁的时候,都让线程遵守上述的顺序,此时的循环等待就自然破除。

回到我们的上一个故事,关于获取酱油和获取醋的例子,我们只需要将代码进行更改,让两个人获取锁的执行顺序是一样的即不会产生死锁的情况。

代码执行的结果:

以上的内容就是关于死锁的内容。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西西¥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值