• Java源码剖析34讲学习笔记~6


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

    死锁

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

    死锁

    演示代码

    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 读写锁允许同一时间内有多个线程进行读操作, 属于共享锁, 可以理解为乐观锁
  • 相关阅读:
    滚动相册
    智能拼图
    连连看
    魔法色块
    ASP.NET Session的七点认识
    从11月开始windows update速度缓慢,中国反击?
    WM实现文件关联
    与黄河老师合影
    ubuntu8.04笔记本开启无线网卡记得开网卡的电源
    无法在Web服务器上启动调试,与Web服务器通信时出现身份验证错误
  • 原文地址:https://www.cnblogs.com/unrecognized/p/13322990.html
Copyright © 2020-2023  润新知