zoukankan      html  css  js  c++  java
  • 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 读写锁允许同一时间内有多个线程进行读操作, 属于共享锁, 可以理解为乐观锁
  • 相关阅读:
    剑指 Offer——13. 调整数组顺序使奇数位于偶数前面
    剑指 Offer——3. 从尾到头打印链表
    剑指 Offer——2. 替换空格
    剑指 Offer——1. 二维数组中的查找
    LeetCode 905. Sort Array By Parity 按奇偶校验排列数组
    LeetCode 448. Find All Numbers Disappeared in an Array找到所有数组中消失的元素
    SSH 代码笔记
    anaconda3安装caffe
    opencv多版本安装
    人脸文章与数据库
  • 原文地址:https://www.cnblogs.com/unrecognized/p/13322990.html
Copyright © 2011-2022 走看看