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
        }
        
    • 乐观锁

      扫描二维码关注公众号,回复: 12433468 查看本文章
      • 乐观锁认为一般情况下数据在修改时不会出现冲突, 所以在数据访问之前不会加锁, 只是在数据提交更改时, 才会对数据进行检测, 因此不会造成死锁

      • 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 读写锁允许同一时间内有多个线程进行读操作, 属于共享锁, 可以理解为乐观锁

猜你喜欢

转载自blog.csdn.net/CJG753951/article/details/107384323
今日推荐