线程安全以及死锁

什么是线程安全

        在高并发场景下, 线程的执行结果是确定的, 符合期望的;反之, 线程不安全可能造成违背直接的结果.

什么是线程安全的类

        当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在调用代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

如何实现线程安全的类

  • 线程封闭: 把对象封装到线程里面, 只有唯一一个线程可以看到这个对象.就算这个对象不是线程安全的也能实现线程安全, 因为在单线程环境下, 不存在并发竞争问题.
  • 栈封闭: 在线程里面创建的局部变量都是线程安全的
  • ThreadLocal: ThreadLocal是用一个Map实现的, key对应线程, value对应缓存的对象, 每个线程都有独立的value对象, 实现了类似线程封闭的效果.注意:ThreadLocal本身是线程安全的, 但是外部逻辑可能对缓存对象做线程不安全操作. 
  • 无状态的类: 没有属性的类叫做无状态类, 这个类只可能有成员方法, 实现了类似栈封闭的效果
  • 不可变的类: 所有成员变量加上final关键字, 不可修改也就没有并发对写问题. 注意: 如果成员变量是对象, final只能保证对象的引用不可更改, 而对象成员属性依然可能存在并发问题.
  • 加锁或CAS: 加内置锁或者显式锁或者CAS, 保证代码块或者方法原子性

死锁

死锁: 两个以上线程互相持有对方的锁, 并永不释放, 这种现象可叫做死锁.

学术定义:

  1. 互斥条件
  2. 请求和保持条件
  3. 不剥夺条件
  4. 环路等待条件

手写死锁代码:

/**
 * @Author kk
 * @Date 2023-10-14
 * 手写死锁示例
 */
public class DeadLockDemo {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void playA() {
        synchronized (lock1) {
            System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
            SleepTools.second(1);
            synchronized (lock2) {
                System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
            }
        }
    }

    public static void playB() {
        synchronized (lock2) {
            System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
            SleepTools.second(1);
            synchronized (lock1) {
                System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
            }
        }
    }

    public static void main(String[] args) {
        new Thread(DeadLockDemo::playA).start();
        playB();
    }
}

监控死锁

  • jdk命令: jstack pid
  • arthas命令: thread -b

解决死锁:

        解决死锁的方案也就是推翻前面说的死锁原则

  • 获取多个锁的顺序保证一致
  • 获取锁失败后释放锁

        有序占锁示例:

/**
 * @Author kk
 * @Date 2023-10-14
 * 打破死锁示例-有序占锁
 */
public class DeadLockBreakDemo1 {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    private void playA() {
        synchronized (lock1) {
            System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
            SleepTools.second(1);
            synchronized (lock2) {
                System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
            }
        }
    }

    private void playB() {
        synchronized (lock1) {
            System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
            SleepTools.second(1);
            synchronized (lock2) {
                System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
            }
        }
    }

    public static void main(String[] args) {
        DeadLockBreakDemo1 demo = new DeadLockBreakDemo1();
        new Thread(demo::playA).start();
        demo.playB();
    }
}

        抢锁失败释放占有锁示例:

/**
 * @Author kk
 * @Date 2023-10-14
 * 打破死锁示例-抢锁失败释放锁
 */
public class DeadLockBreakDemo2 {

    private static final Lock lock1 = new ReentrantLock();
    private static final Lock lock2 = new ReentrantLock();

    private static final Random R = new Random();

    public void playA() throws InterruptedException {
        while (true) {
            if (lock1.tryLock()) {
                try {
                    System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
                    if (lock2.tryLock()) {
                        System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
                        lock2.unlock();
                        System.out.println(ThreadTools.getThreadName() + "释放了lock2");
                        break;
                    }
                } finally {
                    lock1.unlock();
                    System.out.println(ThreadTools.getThreadName() + "释放了lock1");
                }
            }
            Thread.sleep(R.nextInt(10));
        }
    }

    public void playB() throws InterruptedException {
        while (true) {
            if (lock2.tryLock()) {
                try {
                    System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
                    if (lock1.tryLock()) {
                        System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
                        lock1.unlock();
                        System.out.println(ThreadTools.getThreadName() + "释放了lock1");
                        break;
                    }
                } finally {
                    lock2.unlock();
                    System.out.println(ThreadTools.getThreadName() + "释放了lock2");
                }
            }
            Thread.sleep(R.nextInt(50));
        }
    }

    public static void main(String[] args) throws Exception {
        DeadLockBreakDemo2 demo = new DeadLockBreakDemo2();
        new Thread(() -> {
            try {
                demo.playA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        demo.playB();
    }
}

打破死锁可能存在的问题

  • 动态顺序占锁: 加锁代码是有序, 但是加锁对象可动态传参改变, 见下方示例
  • 活锁: 多线程同时获取第一把锁成功, 同时获取第二把锁失败, 重复循环, 线程是RUNNABLE状态.

        动态顺序占锁示例:

/**
 * @Author kk
 * @Date 2023-10-14 3:53
 * 动态顺序死锁示例
 */
public class DeadLockTrendsDemo {

    private void playA(Object lock1, Object lock2) {
        synchronized (lock1) {
            System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
            SleepTools.second(1);
            synchronized (lock2) {
                System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
            }
        }
    }

    private void playB(Object lock1, Object lock2) {
        synchronized (lock1) {
            System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
            SleepTools.second(1);
            synchronized (lock2) {
                System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
            }
        }
    }

    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        DeadLockTrendsDemo demo = new DeadLockTrendsDemo();
        new Thread(() -> demo.playA(lock1, lock2)).start();
        demo.playB(lock2, lock1);
    }
}

        解决动态顺序占锁: 给加锁对象hash排序, 按顺序获取锁

/**
 * @Author kk
 * @Date 2023-10-14 3:53
 * 动态顺序死锁 - 对象手动排序后加锁解决
 */
public class DeadLockTrendsFixDemo {

    private static final Object lock3 = new Object();

    private void playA(Object lock1, Object lock2) {
        List<Object> lockList = sortObj(lock1, lock2);
        if (lockList != null) {
            synchronized (lockList.get(0)) {
                System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
                SleepTools.second(1);
                synchronized (lockList.get(1)) {
                    System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
                }
            }
        } else {
            // hash 相等, 引用第三方锁
            synchronized (lock3) {
                synchronized (lock1) {
                    System.out.println(ThreadTools.getThreadName() + "拿到了lock1");
                    SleepTools.second(1);
                    synchronized (lock2) {
                        System.out.println(ThreadTools.getThreadName() + "拿到了lock2");
                    }
                }
            }
        }
    }

    // 按hash排序, 升序存到列表; 若hash相等, 返回null
    private List<Object> sortObj(Object lock1, Object lock2) {
        int hash1 = System.identityHashCode(lock1);
        int hash2 = System.identityHashCode(lock2);
        if (hash1 == hash2) {
            return null;
        } else if (hash1 > hash2) {
            return Arrays.asList(lock2, lock1);
        } else {
            return Arrays.asList(lock1, lock2);
        }
    }

    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        DeadLockTrendsFixDemo demo = new DeadLockTrendsFixDemo();
        new Thread(() -> demo.playA(lock1, lock2)).start();
        demo.playA(lock2, lock1);
    }
}

        解决活锁问题: 获取锁失败后, 休眠随机数, 避免同一时刻竞争一把锁,代码参考上面DeadLockBreakDemo2 

线程饥饿

        线程优先级低导致长时间分不到CPU时间片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值