Java多线程 - 锁与死锁

前言

上一次,我们讲到了一些线程安全的问题,以及导致线程不安全的原因,这一次,我们来讲解,如何来线程变得更安全。

使用synchoronized加锁

首先说明 synchoronized 不是一个类,也不是一个方法,他是一个关键字,类似于static,可以用于修饰,代码块,方法等等

使用方法

  1. 修饰普通的方法

    1. 使用synchronized的时候,本质上是在针对某个对象进行加锁
    2. 此时锁对象就是 this
      在这里插入图片描述
  2. 修饰一个代码块

    1. 需要显示指定针对哪个对象加锁,不同的锁对象,可能会产生不一样的效果
  3. 修饰一个静态方法

    1. 针对当前类的类对象加锁,也就是xx.class (反射)

volatile关键字

使用synchoronized不同的是不是一个锁,而是一种标记,被修饰的变量,是所谓"不稳定的",也就是说,可能会发生变化,如果不及时的感知到变量的变化,可能会导致线程安全问题,观察图片总结volatile的作用,让程序对此变量"敏感"
在这里插入图片描述
在这里插入图片描述

不可随意使用锁!!!!

死锁

产生死锁的条件

  1. 互斥使用:一个锁被一个线程占用了以后,其他线程无法占用
  2. 不可抢占:一个锁被一个线程占用了以后,其他的线程不能把这个锁给抢走
  3. 请求和保持:当一个线程占据了多把锁以后,除非显示的释放锁,否则这些锁始终都是该被线程持有的
  4. 环路等待: 等待关系成环了

死锁案例

以下是一个很简单的死锁的案例,在这个案例中,我们先创建一个DeadLock对象,并创建两个线程,各自调用lockAlockB方法
lockA方法中,先是取得了锁a 然后 睡眠一会(不然执行的太快,还没开始竞争就结束了),然后等线程睡醒了以后,就会试图去取得锁b,但是这个时候,锁b已经被B线程取走了,所以这个时候A线程就会去等待B线程释放锁,但是这个时候B线程也在等待A线程释放锁,这个时候,就形成了死锁,以下是是实现代码,以及 运行截图

package org.example.thread;
class DeadLock{
    public Object a = new Object();
    public Object b = new Object();
    public void lockA(){
        synchronized (a){
            System.out.println("得到锁A1");
            try {
                Thread.sleep(3000);
                System.out.println("睡好了 我要获得锁1!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (b){
                System.out.println("得到锁B1");
            }
            System.out.println("我即将释放锁1");
        }
    }
    public void lockB(){
        synchronized (b){
            System.out.println("得到锁B2");
            try {
                Thread.sleep(3000);
                System.out.println("睡好了 我要获得锁2!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (a){
                System.out.println("得到锁A2");
            }
            System.out.println("我即将释放锁2");
        }
    }
}
public class Demo27 {
    public static void main(String[] args) {
        DeadLock d = new DeadLock();
        new Thread(d::lockA).start();
        new Thread(d::lockB).start();

    }

}

在这里插入图片描述

如何解决死锁?

想要解决死锁,主要还是要从第四点切入,也就是,避免环路等待,避免出现
也就是说

  1. 如果需要多把锁相互配合,那么加锁时,规定好固定的顺序,所有的线程都遵守这个顺序去进行加锁,就不会出现换路等待
  2. 尽量少的使用嵌套锁
    上述代码就可以优化为
package org.example.thread;
class DeadLock2{
    public Object a = new Object();
    public Object b = new Object();
    public void lockA(){
        synchronized (a){
            System.out.println("得到锁A1");
            try {
                Thread.sleep(3000);
                System.out.println("睡好了 我要获得锁1!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (b){
                System.out.println("得到锁B1");
            }
            System.out.println("我即将释放锁1");
        }
    }
    public void lockB(){
        synchronized (a){
            System.out.println("得到锁B2");
            try {
                Thread.sleep(3000);
                System.out.println("睡好了 我要获得锁2!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (b){
                System.out.println("得到锁A2");
            }
            System.out.println("我即将释放锁2");
        }
    }
}
public class Demo28 {
    public static void main(String[] args) {
        DeadLock2 d = new DeadLock2();
        Thread A = new Thread(d::lockA);
        Thread B = new Thread(d::lockB);
        A.start();
        B.start();

    }

}

运行结果如下
在这里插入图片描述

标准库中的线程安全问题

在Java标准库中,不是所有的类,都是线程安全的,如果再多线程的情况下使用这些类,可能会出现一些问题

  1. 线程不安全的
    • ArrayList
    • LinkedList
    • HashMap
    • TreeMap
    • HashSet
    • TreeSet
    • StringBuilder
  2. 线程安全的 也就是在一些可能出现线程安全问题的方法上 使用了synchoronized关键字
    • Vector
    • HashTable
    • ConcurrentHashMap
    • StringBuffer
    • String (不包含synchoronized关键字,因为String本身是不可变对象)
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值