该昵称无法识别

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

谈谈你对锁的理解,如何手动模拟一个死锁

死锁

指两个线程同时占用两个资源又在彼此等待对方释放锁资源

死锁

演示代码

public class LockExample {
    public static void main (String[] args) {
        deadLock(); // 死锁
    }
    
    private static void deadLock() {
        Object lock1 = new Object();
        Object lock2 = new Object();
        
        // 线程一拥有lock1 试图获取lock2
        new Thread(() -> {
            synchronized(lock1) {
                System.out.println("获取lock1成功");
                try{
                    TimeUnit.SECONDS.sleep(3);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 试图获取锁lock2
                synchronized(lock2) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();
        
        // 线程二lock2试图获取lock1
        new Thread(() -> {
            synchronized(lock2) {
                System.out.println("获取lock2成功");
                try{
                    TimeUnit.SECONDS.sleep(3);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 试图获取锁lock2
                synchronized(lock1) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();
    }
}

锁相关面试问题

  • 什么是乐观锁和悲观锁? 他们的应用都有哪些? 乐观锁有什么问题?

    • 悲观锁

      • 指的是数据对外界的修改采取保守策略, 它认为线程很容易会把数据修改掉, 因此在整个数据被修改的过程中都会采用锁定状态, 直到一个线程使用完, 其他线程才可以继续使用

      • 代码如下:

        public class LockExample {
            public static void main(String[] args) {
                synchronized(LockExample.class) {
                    System.out.println("lock");
                }
            }
        }
        
      • 反编译结果如下:

        Complied from "LockExample.java"
        public class com.example.interview.ext.LockExample{
            public com.example.interview.ext.LockExample();
             Code:
              0: aload_0
              1: invokespecial #1	// Method java/lang/Object."<init>";()V
              4: return
            public static void main(java.lang.String[]);
             Code:
              0: ldc	#2	// class com/example/interview/ext/LockExample
              2: dup
              3: astore_1
              4: monitorenter // 加锁
              5: getstatic	#3	// Field java/lang/System.out:Ljava/io/PrintStream;
              8: ldc	#4	// String lock
              10: invokevirtual	#5	// Method java/io/PrintStream.println:(Ljava/lang/String;)V
              13: aload_1
              14: monitorexit	// 释放锁
              15: goto	23
              18: astore_2
              19: aload_1
              20: monitorexit
              21: aload_2
              22: athrow
              23: return
             Exception table:
              from to target type
               5	15	18	any
               18	21	18	any
        }
        
    • 乐观锁

      • 乐观锁认为一般情况下数据在修改时不会出现冲突, 所以在数据访问之前不会加锁, 只是在数据提交更改时, 才会对数据进行检测, 因此不会造成死锁

      • Java中的乐观锁大部分是通过 CAS (Compare And Swap, 比较并交换) 操作实现

      • CAS 是一个多线程同步的原子指令, CAS操作包含三个重要的信息: 内存位置, 预期原值, 新值

      • CAS 衍生问题 ABA的常见处理方式: 添加版本号, 每次修改之后更新版本号

      • JDK在1.5时提供了 AtomicStamedReference 类也可以解决 ABA 的问题, 此类维护了一个"版本号" Stamp, 每次在比较时不止比较当前值还比较版本号

        public class AtomicStampedReference<V> {
            public static class Pair<V> {
                final T reference;
                final int stamp;	// 版本号
                private Pair(T reference, int stamp){
                    this.reference = reference;
                    this.stamp = stamp;
                }
                static <T> Pair<T> of(T reference, int stamp) {
                    return new Pair<T>(reference, stamp);
                }
            }
            // 比较并设置
            public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp){
                Pair<V> current = pair;
                return expectedREference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp)));
            }
            // ...其他源码
        }
        
  • 什么是可重入锁? 用代码如何实现? 他的实现原理是什么?

    • 可重入锁

      • 也叫递归锁, 指的是同一个线程, 如果外面的函数拥有此锁之后, 内层的函数也可以继续获取该锁, 在 Java 语言中 ReentrantLock 和 synchronized 都是可重入锁

        /**
         * synchronized  演示可重入锁
         */
        public class LockExample{
            public static void main(String[] args) {
                reentrantA();	// 可重入锁
            }
            /**
             * 可重入锁A方法
             */
            private synchronized static void reentrantA() {
                System.out.println(Thread.currentThread().getName() + "执行 reentrantA");
                reentrantB();
            }
            /**
             * 可重入锁A方法
             */
            private synchronized static void reentrantB() {
                System.out.println(Thread.currentThread().getName() + "执行 reentrantB");
            }
        }
        
      • 可重入锁内部维护了一个计数器, 每加一重锁计数器 +1, 退出则 -1, 当计数器为 0 时, 则表示当前对象未加锁

  • 什么是共享锁和独占锁?

    • 独占锁

      • 只能被单线程持有的锁, 指的是在任何时候, 最多只能有一个线程持有该锁, ReentrantLock 就是独占锁, 可以理解为悲观锁
    • 共享锁

      • 可以被多线程持有的锁, ReadWriteLock 读写锁允许同一时间内有多个线程进行读操作, 属于共享锁, 可以理解为乐观锁
posted on 2020-07-16 15:42  Java全栈路线  阅读(145)  评论(0编辑  收藏  举报