Java 的锁 -- 具体的锁对象及其实现原理

目录

  • Java 的锁 -- 具体的锁对象及其实现原理
    • synchronized 关键字
      • synchronized 的作用
      • synchronized 的基本语法(使用)
        • 作用于 实例方法
        • 作用于 静态方法
        • 作用于 代码块
      • synchronized 的原理
        • monitor
          • monitor 机制
          • monitor 作用
          • 观察写好的 锁测试文件的 字节码
          • monitorenter 指令 和 monitorexit 指令
        • 总结
    • ReentrantLock 类对象
      • ReentrantLock 的使用语法
      • ReentrantLock 类对象的常用方法
      • ReentrantLock 是可重入锁
        • 两个平行方法争夺锁的情况
        • 嵌套方法争夺锁的情况
      • ReentrantLock 可中断
      • ReentrantLock 可以设置获取锁的时间,超时就结束
      • ReentrantLock 支持多个变量
      • ReentrantLock 公平锁和非公平锁
    • ReadWriteLock 接口
      • 无锁的例子
      • 添加了读写锁的例子
      • 锁的创建与配置
        • 读锁
        • 写锁
        • 锁降级
    • volatile 关键字
    • 比较
      • ReentrantLock 和 synchronized 的比较

Java 的锁 – 具体的锁对象及其实现原理

  • 锁机制:用来保证 在多线程并发情况下数据的一致性

  • 锁的作用点:操作一个对象或者调用一个方法前加锁,这样当其他线程也对该对象和方法进行访问时就需要获得锁,如果该锁被其他线程持有,那么该线程则进入阻塞队列等待获得锁

  • Java 中,用作锁的对象有:synchronized、ReentrantLock、ReadWriteLock、volatile。

  • 并发编程三大特征:原子性、可见性、有序性

synchronized 关键字

  • synchronized,中文意思:同步的,也被称为:同步锁

  • synchronized 锁是 Java 中解决并发问题的一种最常用的方法,也是最简单的一种方法

  • synchronized 关键字只能修饰 方法代码块

  • synchronized 锁不能中断,只能等待同步代码块的结束

synchronized 的作用

  • 原子性:确保线程互斥地访问同步代码。

    • 因为 synchronized 是一个独占锁
  • 可见性:保证共享变量的修改能够及时可见。

    • 在 Java 内存模型中,对一个变量 unlock(解锁)操作之前,必须要同步到主内存中

    • 如果对一个变量进行 lock(加锁)操作,则将会清空工作内存中此变量的值

    • 故,在执行引擎使用此变量前,需要重新从主内存中 load 操作或 assign 操作初始化变量值 来保证的变量的修改能够及时可见

  • 有序性:有效解决重排序问题(处理器为提高运算速度而做出违背代码原有顺序的优化,在正常情况下是不对结果造成影响的)。

    • 但是,锁的 unlock 操作是不能先于 lock 操作发生的。

    • 重排序需要遵守 as-if-serial 规则和 happens-before 规则,目的是为了提高性能。

synchronized 的基本语法(使用)

  • synchronized 在 Java 中,是以关键字的形式出现的

  • synchronized 可以修饰 实例方法静态方法代码块静态代码块

作用于 实例方法
  • 锁作用于当前对象实例。此时 synchronized 是对象锁

  • synchronized 修饰一个实例方法时,这个实例方法便被加上了同步锁,意味着某一时刻只有一个线程可以操作访问这个实例方法

public class LockTest {
	
	public synchronized void method() {
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(5000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			System.out.println("method 给实例方法上锁");
		}
	}

}
  • 直接添加 synchronized 关键字到指定位置即可。

  • synchronized 关键字添加在 void(返回值)关键字前,public(访问权限)关键字后

作用于 静态方法
  • 锁作用于类的 Class 实例。此时 synchronized 是类锁

  • 静态方法是由每个类唯一的 Class 对象调用的。

  • synchronized 修饰一个静态方法时,这个静态方法所属的类便被加上了同步锁,意味着某一时刻只有一个线程可以操作访问这个类 Class 对象

public class LockTest {
	
	public static synchronized void methodA() {
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(5000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			System.out.println("methodA 给静态方法上锁");
		}
	}
}
  • 直接添加 synchronized 关键字到指定位置即可。

  • synchronized 关键字添加在 void(返回值)关键字前,static(静态的)关键字后

作用于 代码块
  • 锁作用于当前指定加锁的对象,可以是当前实例对象,也可以是其他任意的实例对象

  • synchronized 修饰一个代码块时,表示只有当前对象(object)才可以访问这段代码块(大括号内的逻辑代码)

public class LockTest {
	
	public void methodB() {
		synchronized(this) {
			try {
				System.out.println(Thread.currentThread().getName());
				Thread.sleep(5000);
			} catch(InterruptedException e) {
				e.printStackTrace();
			} finally {
				System.out.println("methodB 给代码块上锁");
			}
		}
	}

}
  • 添加方式为:synchronized(object){},括号内是指定的加锁对象,大括号内是需要加锁的代码块。

  • synchronized 关键字添加在 void(返回值)关键字前,static(静态的)关键字后

synchronized 的原理

  • 首先,synchronized 锁的线程安全的语义实现最终依赖的都是 monitor,它才是真正意义上的锁

  • synchronized 正是对 Monitor 机制的一种实现

  • 在 Java 中,每一个对象都会关联一个监视器(monitor)

monitor
  • monitor:监视器、管程

  • Monitor 作为一种同步机制,它并非 Java 所特有,但 Java 实现了这一机制(synchronized)

monitor 机制
  • 同一时刻,只有一个 进程/线程 能进入 monitor 中定义的临界区

  • 这个临界区是 synchronized 关键字最终所指点的,不管是方法还是代码块。被 synchronized 关键字修饰的方法、代码块,就是 monitor 机制的临界区

  • monitor 机制需要几个元素来配合,分别是:

    • 临界区

    • monitor 对象及锁

    • 条件变量以及定义在 monitor 对象上的 wait,signal 操作

monitor 作用
  • 互斥每次只允许一个线程进入临界区

  • 协作当临界区的线程执行结束后满足特定条件时,可以通知其他的等待线程进入

观察写好的 锁测试文件的 字节码
  • 第一步:将 LockTest.java 另存一份 ASNI(电脑默认的编码,这样才可以被识别)。

  • 第二步:在 cmd 控制台运行 javac LockTest.java(cmd 控制台是在 LockTest.java 文件所在的包中打开的)。

  • 第三步:在 cmd 控制台运行 javap -v LockTest.class。

>javac LockTest.java

>javap -v LockTest.class
  • 我们可以看到 synchronized 作用于代码块的方法 methodB()monitorenter 指令monitorexit 指令 把临界区的代码包裹起来。

  • 我们可以看到 synchronized 作用于方法的方法 method()、methodA()flags 标志中有一个共同的标志 ACC_SYNCHRONIZED

public class com.example.demo.LockTest
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #49                         // com/example/LockTest
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 5, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // java/lang/System.out:Ljava/io/PrintStream;
   #8 = Class              #10            // java/lang/System
   #9 = NameAndType        #11:#12        // out:Ljava/io/PrintStream;
  #10 = Utf8               java/lang/System
  #11 = Utf8               out
  #12 = Utf8               Ljava/io/PrintStream;
  #13 = Methodref          #14.#15        // java/lang/Thread.currentThread:()Ljava/lang/Thread;
  #14 = Class              #16            // java/lang/Thread
  #15 = NameAndType        #17:#18        // currentThread:()Ljava/lang/Thread;
  #16 = Utf8               java/lang/Thread
  #17 = Utf8               currentThread
  #18 = Utf8               ()Ljava/lang/Thread;
  #19 = Methodref          #14.#20        // java/lang/Thread.getName:()Ljava/lang/String;
  #20 = NameAndType        #21:#22        // getName:()Ljava/lang/String;
  #21 = Utf8               getName
  #22 = Utf8               ()Ljava/lang/String;
  #23 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #24 = Class              #26            // java/io/PrintStream
  #25 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = Long               5000l
  #31 = Methodref          #14.#32        // java/lang/Thread.sleep:(J)V
  #32 = NameAndType        #33:#34        // sleep:(J)V
  #33 = Utf8               sleep
  #34 = Utf8               (J)V
  #35 = String             #36            // methodA 给静态方法上锁
  #36 = Utf8               methodA 给静态方法上锁
  #37 = Class              #38            // java/lang/InterruptedException
  #38 = Utf8               java/lang/InterruptedException
  #39 = Methodref          #37.#40        // java/lang/InterruptedException.printStackTrace:()V
  #40 = NameAndType        #41:#6         // printStackTrace:()V
  #41 = Utf8               printStackTrace
  #42 = String             #43            // method 给实例方法上锁
  #43 = Utf8               method 给实例方法上锁
  #44 = String             #45            // methodB 给代码块上锁
  #45 = Utf8               methodB 给代码块上锁
  #46 = String             #47            // VolatileLock
  #47 = Utf8               VolatileLock
  #48 = Fieldref           #49.#50        // com/example/LockTest.LOCK_NAME:Ljava/lang/String;
  #49 = Class              #51            // com/example/LockTest
  #50 = NameAndType        #52:#53        // LOCK_NAME:Ljava/lang/String;
  #51 = Utf8               com/example/LockTest
  #52 = Utf8               LOCK_NAME
  #53 = Utf8               Ljava/lang/String;
  #54 = Utf8               Code
  #55 = Utf8               LineNumberTable
  #56 = Utf8               methodA
  #57 = Utf8               StackMapTable
  #58 = Class              #59            // java/lang/Throwable
  #59 = Utf8               java/lang/Throwable
  #60 = Utf8               method
  #61 = Utf8               methodB
  #62 = Utf8               
  #63 = Utf8               SourceFile
  #64 = Utf8               LockTest.java
{
  public com.example.demo.LockTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 8: 0

  public static synchronized void methodA();
    descriptor: ()V
    flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=2, args_size=0
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: invokestatic  #13                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
         6: invokevirtual #19                 // Method java/lang/Thread.getName:()Ljava/lang/String;
         9: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: ldc2_w        #29                 // long 5000l
        15: invokestatic  #31                 // Method java/lang/Thread.sleep:(J)V
        18: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        21: ldc           #35                 // String methodA 给静态方法上锁
        23: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        26: goto          56
        29: astore_0
        30: aload_0
        31: invokevirtual #39                 // Method java/lang/InterruptedException.printStackTrace:()V
        34: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        37: ldc           #35                 // String methodA 给静态方法上锁
        39: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        42: goto          56
        45: astore_1
        46: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        49: ldc           #35                 // String methodA 给静态方法上锁
        51: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        54: aload_1
        55: athrow
        56: return
      Exception table:
         from    to  target type
             0    18    29   Class java/lang/InterruptedException
             0    18    45   any
            29    34    45   any
      LineNumberTable:
        line 15: 0
        line 16: 12
        line 20: 18
        line 21: 26
        line 17: 29
        line 18: 30
        line 20: 34
        line 21: 42
        line 20: 45
        line 21: 54
        line 22: 56
      StackMapTable: number_of_entries = 3
        frame_type = 93 /* same_locals_1_stack_item */
          stack = [ class java/lang/InterruptedException ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 10 /* same */

  public synchronized void method();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=3, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: invokestatic  #13                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
         6: invokevirtual #19                 // Method java/lang/Thread.getName:()Ljava/lang/String;
         9: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: ldc2_w        #29                 // long 5000l
        15: invokestatic  #31                 // Method java/lang/Thread.sleep:(J)V
        18: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        21: ldc           #42                 // String method 给实例方法上锁
        23: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        26: goto          56
        29: astore_1
        30: aload_1
        31: invokevirtual #39                 // Method java/lang/InterruptedException.printStackTrace:()V
        34: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        37: ldc           #42                 // String method 给实例方法上锁
        39: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        42: goto          56
        45: astore_2
        46: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        49: ldc           #42                 // String method 给实例方法上锁
        51: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        54: aload_2
        55: athrow
        56: return
      Exception table:
         from    to  target type
             0    18    29   Class java/lang/InterruptedException
             0    18    45   any
            29    34    45   any
      LineNumberTable:
        line 26: 0
        line 27: 12
        line 31: 18
        line 32: 26
        line 28: 29
        line 29: 30
        line 31: 34
        line 32: 42
        line 31: 45
        line 32: 54
        line 33: 56
      StackMapTable: number_of_entries = 3
        frame_type = 93 /* same_locals_1_stack_item */
          stack = [ class java/lang/InterruptedException ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 10 /* same */

  public void methodB();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: invokestatic  #13                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        10: invokevirtual #19                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        13: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        16: ldc2_w        #29                 // long 5000l
        19: invokestatic  #31                 // Method java/lang/Thread.sleep:(J)V
        22: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        25: ldc           #44                 // String methodB 给代码块上锁
        27: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: goto          60
        33: astore_2
        34: aload_2
        35: invokevirtual #39                 // Method java/lang/InterruptedException.printStackTrace:()V
        38: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        41: ldc           #44                 // String methodB 给代码块上锁
        43: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        46: goto          60
        49: astore_3
        50: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        53: ldc           #44                 // String methodB 给代码块上锁
        55: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        58: aload_3
        59: athrow
        60: aload_1
        61: monitorexit
        62: goto          72
        65: astore        4
        67: aload_1
        68: monitorexit
        69: aload         4
        71: athrow
        72: return
      Exception table:
         from    to  target type
             4    22    33   Class java/lang/InterruptedException
             4    22    49   any
            33    38    49   any
             4    62    65   any
            65    69    65   any
      LineNumberTable:
        line 36: 0
        line 38: 4
        line 39: 16
        line 43: 22
        line 44: 30
        line 40: 33
        line 41: 34
        line 43: 38
        line 44: 46
        line 43: 49
        line 44: 58
        line 45: 60
        line 46: 72
      StackMapTable: number_of_entries = 5
        frame_type = 255 /* full_frame */
          offset_delta = 33
          locals = [ class com/example/LockTest, class java/lang/Object ]
          stack = [ class java/lang/InterruptedException ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 10 /* same */
        frame_type = 68 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 6

  static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #46                 // String VolatileLock
         2: putstatic     #48                 // Field LOCK_NAME:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 10: 0
}
SourceFile: "LockTest.java"
monitorenter 指令 和 monitorexit 指令
  • monitorenter 指令:线程尝试去获取 monitor

  • monitorexit 指令:拥有 monitor 的线程归还 monitor

  • 执行到 monitorenter 指令的线程,会尝试去获得对应的 monitor

    • 每个对象维护着一个记录着被锁次数的计数器, 对象未被锁定时,该计数器为 0。

    • 线程进入 monitor(执行 monitorenter 指令)时,会把计数器设置为 1。

    • 当同一个线程再次获得该对象的锁的时候,计数器再次自增。

    • 当其他线程想获得该 monitor 的时候,就会阻塞,直到计数器为 0 才能成功。

  • monitor 的拥有者线程才能执行 monitorexit 指令

    • 线程执行 monitorexit 指令,就会让 monitor 的计数器减一。

    • 如果计数器为 0,表明该线程不再拥有 monitor。其他线程就允许尝试去获得该 monitor 了。

总结
  • 同步代码块(synchronized 作用于 代码块)是通过 monitorenter 和 monitorexit 来实现

    • 当线程执行到 monitorenter 的时候要先获得 monitor 锁,才能执行后面的方法。

    • 当线程执行到 monitorexit 的时候则要释放锁。

  • 同步方法(synchronized 作用于 实例方法和静态方法)是通过中设置 ACC_SYNCHRONIZED 标志来实现

    • 当线程执行有 ACC_SYNCHRONI 标志的方法,需要获得 monitor 锁。

    • 每个对象维护一个加锁计数器,为 0 表示可以被其他线程获得锁,不为 0 时,只有当前锁的线程才能再次获得锁。

    • 同步方法和同步代码块底层都是通过 monitor 来实现同步的。

    • 每个对象都与一个 monitor 相关联,线程可以占有或者释放 monitor。

ReentrantLock 类对象

  • ReentrantLock,中文意思:可重入的,同 synchronized 一样,都是可重入锁

  • ReentrantLock 锁是可以中断的,调用线程的 interrupt 方法来中断等待,继续执行下面的代码

  • ReentrantLock 有公平锁和非公平锁两种形式

  • ReentrantLock 锁支持设置获取锁的等待时间

  • ReentrantLock 锁支持多个线程等待区,支持定向唤醒指定线程

ReentrantLock 的使用语法

  • 第一:reentrantLock.lock(); 获取锁代码后一定要紧跟 try { } finally { } 代码块

  • 第二:try { } 内写业务代码

  • 第三:finally { } 内第一行就要写 reentrantLock.unlock(); 释放锁代码

public class LockTest {
	
	/**
	 * ReentrantLock
	 */
	public static void main(String[] args) {
		// 创建锁对象(公平锁)
		ReentrantLock reentrantLock = new ReentrantLock(true);
		
		// 创建锁对象(非公平锁)
		ReentrantLock reentrantLock1 = new ReentrantLock(false);
		
		// 获取锁
		reentrantLock.lock();
		try {
			// 业务代码
		
		
		} finally {
			// 释放锁
			reentrantLock.unlock();
		}
	}

}
  • ReentrantLock reentrantLock = new ReentrantLock(true); 创建公平锁对象

  • ReentrantLock reentrantLock = new ReentrantLock(); 创建非公平锁对象

ReentrantLock 类对象的常用方法

方法名 作用
lock() 用普通方式获取锁,不能中断,没有获取到就直接结束
tryLock() 定时的获取锁,不能中断,没有获取到就等待,超过设定的时间后就不在等待,还没有获取到就结束(没有参数时,与普通方法无异;传入时间和时间单位时,先等待再结束)
lockInterruptibly() 可中断的获取锁,可以中断
unlock() 释放所占有的锁
newCondition() 新建一个线程等待区

ReentrantLock 是可重入锁

  • 内层方法有权利获得外层方法所占有的锁
两个平行方法争夺锁的情况
  • 可以得到结论:线程想要获得锁,需要前一个占有锁的线程释放锁。
public class LockTest2 {
	
	/**
	 * 公平锁 对象
	 */
	private static final ReentrantLock FAIR_REENTRANT_LOCK = new ReentrantLock(true);
	
	/**
	 * 测试 锁 的获取是那种模式
	 */
	public static void main(String[] args) {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		cachedThreadPool.submit(LockTest2::methodB);
		cachedThreadPool.submit(LockTest2::methodA);
		
		try {
			Thread.sleep(10000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			cachedThreadPool.shutdown();
		}
	}
	
	/**
	 * 测试方法 A
	 */
	public static void methodA() {
		System.out.println("methodA 获取锁");
		FAIR_REENTRANT_LOCK.lock();
		try {
			System.out.println("methodA 业务");
			try {
				Thread.sleep(3000);
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
		} finally {
			FAIR_REENTRANT_LOCK.unlock();
			System.out.println("methodA 释放锁");
		}
	}
	
	/**
	 * 测试方法 B
	 */
	public static void methodB() {
		System.out.println("methodB 获取锁");
		FAIR_REENTRANT_LOCK.lock();
		try {
			System.out.println("methodB 业务");
			try {
				Thread.sleep(6000);
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
		} finally {
			FAIR_REENTRANT_LOCK.unlock();
			System.out.println("methodB 释放锁");
		}
	}

}
嵌套方法争夺锁的情况
  • 可以得到结论:因为 ReentrantLock 是可重入锁,故外层方法占有锁期间,其内层函数也可以获得该锁。

  • 释放锁按照栈空间的特性,先获得锁的后释放锁。

class LockTest3 {
	
	private static final ReentrantLock FAIR_REENTRANT_LOCK = new ReentrantLock(true);
	
	/**
	 * 测试 锁 可重入
	 */
	public static void main1(String[] args) {
		method1();
	}
	
	/**
	 * 测试 锁 获取
	 */
	public static void main(String[] args) {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		cachedThreadPool.submit(LockTest2::methodB);
		cachedThreadPool.submit(LockTest3::method3);
		
		try {
			Thread.sleep(5000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			cachedThreadPool.shutdown();
		}
	}
	
	/**
	 * 测试方法 1
	 */
	public static void method1() {
		System.out.println("method1 获取锁");
		FAIR_REENTRANT_LOCK.lock();
		try {
			System.out.println("method1 业务");
			method2();
		} finally {
			FAIR_REENTRANT_LOCK.unlock();
			System.out.println("method1 释放锁");
		}
	}
	
	/**
	 * 测试方法 2
	 */
	public static void method2() {
		System.out.println("method2 获取锁");
		FAIR_REENTRANT_LOCK.lock();
		try {
			System.out.println("method2 业务");
			method3();
		} finally {
			FAIR_REENTRANT_LOCK.unlock();
			System.out.println("method2 释放锁");
		}
	}
	
	/**
	 * 测试方法 3
	 */
	public static void method3() {
		System.out.println("method3 获取锁");
		FAIR_REENTRANT_LOCK.lock();
		try {
			System.out.println("method3 业务");
			// 暂停 3 秒
			try {
				Thread.sleep(3000);
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
		} finally {
			FAIR_REENTRANT_LOCK.unlock();
			System.out.println("method3 释放锁");
		}
	}

}

ReentrantLock 可中断

  • ReentrantLock 可中断是指:在尝试获取锁的过程中,可以中断该过程,并且执行相关业务

  • reentrantLock.lock() 方法是普通的获取锁方法,不允许中断,强行中断时,直接阻塞

  • reentrantLock.lockInterruptibly() 方法是允许中断的获取锁方法,中断时,直接结束获取锁的操作,继续后续的代码执行

  • 子线程中断时,还在尝试获取锁,但中断指令已经到了,所以中断获取锁。

class InterruptLockTest {
	
	private static final ReentrantLock FAIR_REENTRANT_LOCK = new ReentrantLock(true);
	
	public static void main(String[] args) {
		// 子线程 
		Thread thread = new Thread(() -> {
			try {
				System.out.println("子线程 尝试获取锁(可以中断)");
				FAIR_REENTRANT_LOCK.lockInterruptibly();
				try {
					System.out.println("子线程 获取到锁");
				} finally {
					FAIR_REENTRANT_LOCK.unlock();
				}
			} catch(InterruptedException e) {
				System.out.println("子线程 获取锁过程被打断");
				e.printStackTrace();
			}
		});
		
		System.out.println("主线程 获取锁");
		FAIR_REENTRANT_LOCK.lock();
		try {
			System.out.println("主线程 业务");
			thread.start();
			// 暂停 1 秒
			try {
				Thread.sleep(1000);
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
			// 中断
			thread.interrupt();
			// 暂停 1 秒
			try {
				Thread.sleep(1000);
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
		} finally {
			FAIR_REENTRANT_LOCK.unlock();
			System.out.println("主线程 释放锁");
		}
	}

}

ReentrantLock 可以设置获取锁的时间,超时就结束

  • reentrantLock.tryLock() 方法,无参数时:如果无法获取锁,就立即失败

  • reentrantLock.tryLock(4, TimeUnit.SECONDS) 方法,添加了参数超时时间后:等待时间等于超时时间后,如果无法获取锁,就失败

  • reentrantLock.tryLock() 方法,返回值是 boolean 类型,可以判断是否成功获取锁。

  • 用 tryLock() 可以解决一个经典的死锁问题:哲学家就餐问题

class TryLockTest {
	
	private static final ReentrantLock FAIR_REENTRANT_LOCK = new ReentrantLock(true);
	
	public static void main(String[] args) {
		// 子线程 1
		Thread thread1 = new Thread(() -> {
			System.out.println("子线程 1 尝试获取锁(没有添加超时)");
			if(FAIR_REENTRANT_LOCK.tryLock()) {
				try {
					System.out.println("子线程 1 获取到锁");
				} finally {
					FAIR_REENTRANT_LOCK.unlock();
				}
			} else {
				System.out.println("子线程 1 没有获取到锁");
			}
		});
		
		// 子线程 2
		Thread thread2 = new Thread(() -> {
			System.out.println("子线程 2 尝试获取锁(添加超时了超时时间)");
			try {
				if(FAIR_REENTRANT_LOCK.tryLock(4, TimeUnit.SECONDS)) {
					try {
						System.out.println("子线程 2 获取到锁");
					} finally {
						FAIR_REENTRANT_LOCK.unlock();
					}
				} else {
					System.out.println("子线程 2 没有获取到锁");
				}
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
		});
		
		System.out.println("主线程 获取锁");
		FAIR_REENTRANT_LOCK.lock();
		
		try {
			System.out.println("主线程 业务");
			thread1.start();
			thread2.start();
			// 暂停 3 秒
			try {
				Thread.sleep(3000);
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
		} finally {
			FAIR_REENTRANT_LOCK.unlock();
			System.out.println("主线程 释放锁");
		}
	
	}

}

ReentrantLock 支持多个变量

  • 是指:支持多个自定义的线程集合变量,类似于 waitSet(等待线程集合),可以灵活的应对线程等待问题、和唤醒指定的线程

  • 其中,Condition 对象 就是一个人类似 waitSet 的等待区。

  • 使用要点:

    • 创建一个 ReentrantLock 锁对象private static final ReentrantLock FAIR_REENTRANT_LOCK = new ReentrantLock(true);

    • 接着用锁对象创建线程等待区private static final Condition FOOD_WAITING_CIRCLE = FAIR_REENTRANT_LOCK.newCondition();

    • 随后创建几个标志位,方便操作

    • 然后线程需要等待时,进入相应的等待区,等待区对象调用 await() 方法将线程阻塞并放入等待区,FOOD_WAITING_CIRCLE.await();

    • 最后灵活的唤醒指定的等待区对象,运行等待区中的线程FOOD_WAITING_CIRCLE.signal();

class WaitSetLockTest {
	
	private static final ReentrantLock FAIR_REENTRANT_LOCK = new ReentrantLock(true);
	
	/**
	 * 外卖 等待区
	 */
	private static final Condition FOOD_WAITING_CIRCLE = FAIR_REENTRANT_LOCK.newCondition();
	
	/**
	 * 资料 等待区
	 */
	private static final Condition DATA_WAITING_CIRCLE = FAIR_REENTRANT_LOCK.newCondition();
	
	/**
	 * 外卖 等待区 标志位(true 表示 外卖来了)
	 */
	private static boolean FOOD_WAITING_CIRCLE_STATE = false;
	
	/**
	 * 资料 等待区 标志位(true 表示 资料来了)
	 */
	private static boolean DATA_WAITING_CIRCLE_STATE = false;
	
	
	public static void main(String[] args) {
		// 小南在等外卖
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				FAIR_REENTRANT_LOCK.lock();
				try {
					System.out.println("小南问:外卖来了吗?" + FOOD_WAITING_CIRCLE_STATE);
					while(!FOOD_WAITING_CIRCLE_STATE) {
						System.out.println("小南说:外卖没到,再等等。");
						try {
							FOOD_WAITING_CIRCLE.await();
						} catch(InterruptedException e) {
							e.printStackTrace();
						}
					}
					FOOD_WAITING_CIRCLE_STATE = false;
				} finally {
					FAIR_REENTRANT_LOCK.unlock();
					System.out.println("小南说:外卖来了,我拿走了。");
				}
			}
		}, "小南");
		
		// 小刘在等资料
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				FAIR_REENTRANT_LOCK.lock();
				try {
					System.out.println("小刘问:资料来了吗?" + DATA_WAITING_CIRCLE_STATE);
					while(!DATA_WAITING_CIRCLE_STATE) {
						System.out.println("小刘说:资料没到,再等等。");
						try {
							DATA_WAITING_CIRCLE.await();
						} catch(InterruptedException e) {
							e.printStackTrace();
						}
					}
					DATA_WAITING_CIRCLE_STATE = false;
				} finally {
					FAIR_REENTRANT_LOCK.unlock();
					System.out.println("小刘说:资料来了,我拿走了。");
				}
			}
		}, "小刘");
		
		thread1.start();
		thread2.start();
		
		// 暂停 3 秒
		try {
			Thread.sleep(3000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
		
		// 送外卖
		new Thread(new Runnable() {
			@Override
			public void run() {
				FAIR_REENTRANT_LOCK.lock();
				try {
					FOOD_WAITING_CIRCLE_STATE = true;
					System.out.println("外卖来了");
					FOOD_WAITING_CIRCLE.signal();
				} finally {
					FAIR_REENTRANT_LOCK.unlock();
				}
			}
		}, "送外卖").start();
		
		// 送资料
		new Thread(new Runnable() {
			@Override
			public void run() {
				FAIR_REENTRANT_LOCK.lock();
				try {
					DATA_WAITING_CIRCLE_STATE = true;
					System.out.println("资料来了");
					DATA_WAITING_CIRCLE.signal();
				} finally {
					FAIR_REENTRANT_LOCK.unlock();
				}
			}
		}, "送资料").start();
	
	
	}

}

ReentrantLock 公平锁和非公平锁

  • 下面的例子中:new ReentrantLock(true); 创建 公平锁对象;new ReentrantLock(); 创建非公平锁对象

  • 有多个线程竞争锁时,公平锁对象交替的唤醒线程,线程交替的运行

    • 优点:很少出现线程饥饿现象

    • 缺点:频繁的获取、释放锁,可能会影响性能

  • 有多个线程竞争锁时,非公平锁对象不会主动的切换线程,而是让线程自行去获取锁

    • 优点:锁的获取、释放锁并不频繁,一般是按照 CPU 的时间片来执行

    • 缺点:很大可能会出现线程饥饿现象

public class LockTest2 {
	
	/**
	 * 公平锁 对象
	 */
	private static final ReentrantLock FAIR_REENTRANT_LOCK = new ReentrantLock(true);
	
	/**
	 * 非公平锁 对象
	 */
	private static final ReentrantLock NON_FAIR_REENTRANT_LOCK = new ReentrantLock(false);
	
	/**
	 * 测试 ReentrantLock 公平锁
	 */
	public static void mainFair(String[] args) {
		// 公平锁 1
		Thread thread1 = new Thread(new FairLockTest(FAIR_REENTRANT_LOCK), "公平锁 1");
		// 公平锁 1
		Thread thread2 = new Thread(new FairLockTest(FAIR_REENTRANT_LOCK), "公平锁 2");
		thread1.start();
		thread2.start();
	}
	
	/**
	 * 测试 ReentrantLock 非公平锁
	 */
	public static void mainNonFair(String[] args) {
		// 非公平锁 3
		Thread thread3 = new Thread(new FairLockTest(NON_FAIR_REENTRANT_LOCK), "非公平锁 3");
		thread3.start();
		// 非公平锁 4
		Thread thread4 = new Thread(new FairLockTest(NON_FAIR_REENTRANT_LOCK), "非公平锁 4");
		thread4.start();
	}

}

/**
 * 测试 ReentrantLock 公平锁 和 非公平锁
 */
class FairLockTest implements Runnable {
	
	/**
	 * 用来计数
	 */
	private static Integer num = 0;
	
	/**
	 * ReentrantLock 锁对象
	 */
	private final ReentrantLock reentrantLock;
	
	public FairLockTest(ReentrantLock reentrantLock1) {
		this.reentrantLock = reentrantLock1;
	}
	
	@Override
	public void run() {
		while(num <= 1000) {
			reentrantLock.lock();
			try {
				System.out.println(Thread.currentThread().getName() + "," + num);
				num++;
			} finally {
				reentrantLock.unlock();
			}
		}
	}

}

ReadWriteLock 接口

  • ReadWriteLock 中文意思:读写锁

  • ReadWriteLock 提供了:readLockwriteLock 两种锁的操作机制。

    • 读锁:readLock,允许一个资源可以被多个线程同时读,这些读线程之间共享锁,并与所有写线程互斥

    • 写锁:writeLock,一个资源只允许一个线程写,这个写线程与其他的所有线程互斥

  • 不允许同时存在读线程和写线程

  • 添加读写锁的位置:目标资源的读写方法上

  • 优点:提高读线程的效率,因为读锁是共享锁,读读共享

  • 缺点:读线程不可以写当读写线程其中一方数量很少时,该锁会出现饥饿现象

无锁的例子

  • 可以看到,线程的写入和读取都是凌乱的。

  • 同一个写线程不满足原子性原则。

  • 写线程与其他线程不互斥。

  • 读线程中参杂着写线程,没有读写互斥。

public class ReadWriteLockTest {
	
	public static void main(String[] args) {
		// 资源 类
		MyMap myMap = new MyMap();
		
		// 创建线程工厂 1
		TestThreadPollFactory factory1 = new TestThreadPollFactory("读写锁之 写锁");
		// 创建线程池 1
		ExecutorService cachedThreadPool1 = Executors.newCachedThreadPool(factory1);
		
		new Thread(() -> {
			// 十个写线程
			for(int i = 0; i < 10; i++) {
				int finalI = i;
				cachedThreadPool1.submit(() -> myMap.put("tmp" + finalI, Thread.currentThread().getName() + "-" + finalI));
			}
		}).start();
		
		// 创建线程工厂 2
		TestThreadPollFactory factory2 = new TestThreadPollFactory("读写锁之 读锁");
		// 创建线程池 2
		ExecutorService cachedThreadPool2 = Executors.newCachedThreadPool(factory2);
		
		new Thread(() -> {
			// 十个读线程
			for(int i = 0; i < 10; i++) {
				int finalI = i;
				cachedThreadPool2.submit(() -> myMap.get("tmp" + finalI));
			}
		}).start();
		
		try {
			Thread.sleep(2000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			cachedThreadPool1.shutdown();
			cachedThreadPool2.shutdown();
		}
		MyMap.toMapString();
	
	}

}

/**
 * 自定义的 缓存资源(没有加锁)
 */
class MyMap {
	
	/**
	 * 存放 数据的 Map
	 */
	private static volatile Map<String, Object> map = new HashMap<>();
	
	/**
	 * 输出 Map 全部的内容
	 */
	public static void toMapString() {
		System.out.println(map.toString());
	}
	
	/**
	 * 写入 数值
	 *
	 * @param key   键值
	 * @param value 数值
	 */
	public void put(String key, Object value) {
		System.out.println(Thread.currentThread().getName() + " 写入 { " + key + ", " + value + " }");
		map.put(key, value);
		System.out.println(Thread.currentThread().getName() + " 写入完成");
	}
	
	/**
	 * 根据 键值 查找 数值
	 *
	 * @param key 键值
	 */
	public void get(String key) {
		System.out.println(Thread.currentThread().getName() + " 读取 { " + key + " }");
		Object o = map.get(key);
		System.out.println(Thread.currentThread().getName() + " 结果是 { " + o + " }" + " 读取完成");
	}

}

/**
 * 实现线程工厂 ThreadFactory
 * 

* 线程工厂的作用:创建一个线程 */ class TestThreadPollFactory implements ThreadFactory { /** * 线程名称前缀 */ private final String threadNamePrefix; /** * 整数,用来标记线程 */ private final AtomicInteger threadIdx = new AtomicInteger(0); public TestThreadPollFactory(String prefix) { threadNamePrefix = prefix; } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); // 拼接结果 -> 线程工厂 测试,线程:n thread.setName(threadNamePrefix + "线程:" + threadIdx.getAndIncrement()); return thread; } }

添加了读写锁的例子

  • 可以看到:写线程与其他线程互斥。

  • 读线程之间共享锁,与写线程互斥。

public class ReadWriteLockTest {
	
	public static void main(String[] args) {
		// 资源 类
		MyMapLock myMapLock = new MyMapLock();
		
		// 创建线程工厂 1
		TestThreadPollFactory factory1 = new TestThreadPollFactory("读写锁之 写锁");
		// 创建线程池 1
		ExecutorService cachedThreadPool1 = Executors.newCachedThreadPool(factory1);
		new Thread(() -> {
			// 十个写线程
			for(int i = 0; i < 10; i++) {
				int finalI = i;
				cachedThreadPool1.submit(() -> myMapLock.put("tmp" + finalI, Thread.currentThread().getName() + "-" + finalI));
			}
		}).start();
		
		// 创建线程工厂 2
		TestThreadPollFactory factory2 = new TestThreadPollFactory("读写锁之 读锁");
		// 创建线程池 2
		ExecutorService cachedThreadPool2 = Executors.newCachedThreadPool(factory2);
		new Thread(() -> {
			// 十个读线程
			for(int i = 0; i < 10; i++) {
				int finalI = i;
				cachedThreadPool2.submit(() -> myMapLock.get("tmp" + finalI));
			}
		}).start();
		
		try {
			Thread.sleep(2000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			cachedThreadPool1.shutdown();
			cachedThreadPool2.shutdown();
		}
		MyMapLock.toMapString();
	}

}

/**
 * 自定义的 缓存资源(加了读写锁)
 */
class MyMapLock {
	
	/**
	 * 读写锁
	 */
	private static final ReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
	/**
	 * 存放 数据的 Map
	 */
	private static volatile Map<String, Object> map = new HashMap<>();
	
	/**
	 * 输出 Map 全部的内容
	 */
	public static void toMapString() {
		System.out.println(map.toString());
	}
	
	/**
	 * 写入 数值
	 *
	 * @param key   键值
	 * @param value 数值
	 */
	public void put(String key, Object value) {
		// 写 加锁
		REENTRANT_READ_WRITE_LOCK.writeLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + " 写入 { " + key + ", " + value + " }");
			map.put(key, value);
			System.out.println(Thread.currentThread().getName() + " 写入完成");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			// 写 解锁
			REENTRANT_READ_WRITE_LOCK.writeLock().unlock();
		}
	}
	
	/**
	 * 根据 键值 查找 数值
	 *
	 * @param key 键值
	 */
	public void get(String key) {
		// 读 加锁
		REENTRANT_READ_WRITE_LOCK.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + " 读取 { " + key + " }");
			Object o = map.get(key);
			System.out.println(Thread.currentThread().getName() + " 结果是 { " + o + " }" + " 读取完成");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			// 读 解锁
			REENTRANT_READ_WRITE_LOCK.readLock().unlock();
		}
	}

}

/**
 * 实现线程工厂 ThreadFactory
 * 

* 线程工厂的作用:创建一个线程 */ class TestThreadPollFactory implements ThreadFactory { /** * 线程名称前缀 */ private final String threadNamePrefix; /** * 整数,用来标记线程 */ private final AtomicInteger threadIdx = new AtomicInteger(0); public TestThreadPollFactory(String prefix) { threadNamePrefix = prefix; } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); // 拼接结果 -> 线程工厂 测试,线程:n thread.setName(threadNamePrefix + "线程:" + threadIdx.getAndIncrement()); return thread; } }

锁的创建与配置

  • 具体的读写锁对象是:ReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
读锁
  • 加锁:readWriteLock.readLock().lock();

  • 解锁:readWriteLock.readLock().unlock();

  • 读锁是一个共享锁,读线程之间共享锁对象。

  • 读线程不可以写,只能执行读操作

写锁
  • 加锁:readWriteLock.writeLock().lock();

  • 解锁:readWriteLock.writeLock().unlock();

  • 写锁是一个独占锁、互斥锁

  • 写线程可以执行读操作

锁降级
  • 写锁可以降级为读锁,但读锁不能升级为写锁

  • 降级过程:获取写锁 —> 获取读锁 —> 释放写锁 —> 释放读锁

  • 降级过程具体的代码:readWriteLock.writeLock().lock(); —> readWriteLock.readLock().lock();
    —> readWriteLock.readLock().unlock(); —> readWriteLock.writeLock().unlock();

  • 代码中,MyMapLock1 类的 put() 方法是写锁的降级过程

  • 代码中,MyMapLock1 类的 get() 方法是读锁的升级的失败经历。(阻塞)

  • 基本原则:获取写锁后才可以写获取读锁后才可以读

public class ReadWriteLockTest {
	
	public static void main(String[] args) {
		// 资源 类
		MyMapLock1 myMapLock1 = new MyMapLock1();
		
		// 创建线程工厂 1
		TestThreadPollFactory factory1 = new TestThreadPollFactory("读写锁之 写锁(锁降级)");
		// 创建线程池 1
		ExecutorService cachedThreadPool1 = Executors.newCachedThreadPool(factory1);
		new Thread(() -> {
			// 写线程
			cachedThreadPool1.submit(() -> myMapLock1.put("tmp1", Thread.currentThread().getName() + "-1"));
		}).start();
		
		// 创建线程工厂 2
		TestThreadPollFactory factory2 = new TestThreadPollFactory("读写锁之 读锁(普通 读)");
		// 创建线程池 2
		ExecutorService cachedThreadPool2 = Executors.newCachedThreadPool(factory2);
		new Thread(() -> {
			// 读线程
			cachedThreadPool2.submit(() -> myMapLock1.get("tmp1"));
		}).start();
		
		// 创建线程工厂 3
		TestThreadPollFactory factory3 = new TestThreadPollFactory("读写锁之 读锁(尝试 写)");
		// 创建线程池 3
		ExecutorService cachedThreadPool3 = Executors.newCachedThreadPool(factory3);
		new Thread(() -> {
			// 读线程
			cachedThreadPool3.submit(() -> myMapLock1.get("tmp1", "tmp2", Thread.currentThread().getName() + "-2"));
		}).start();
		
		try {
			Thread.sleep(2000);
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			cachedThreadPool1.shutdown();
			cachedThreadPool2.shutdown();
			cachedThreadPool3.shutdown();
		}
		MyMapLock1.toMapString();
	}

}

/**
 * 自定义的 缓存资源(加了读写锁,同时有锁降级)
 */
class MyMapLock1 {
	
	/**
	 * 读写锁
	 */
	private static final ReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
	
	/**
	 * 存放 数据的 Map
	 */
	private static volatile Map<String, Object> map = new HashMap<>();
	
	/**
	 * 输出 Map 全部的内容
	 */
	public static void toMapString() {
		System.out.println(map.toString());
	}
	
	/**
	 * 写入 数值(写锁降级)
	 *
	 * @param key   键值
	 * @param value 数值
	 */
	public void put(String key, Object value) {
		// 写 加锁
		REENTRANT_READ_WRITE_LOCK.writeLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + " 写入 { " + key + ", " + value + " }");
			map.put(key, value);
			System.out.println(Thread.currentThread().getName() + " 写入完成");
			// 锁降级
			// 获取 读锁
			REENTRANT_READ_WRITE_LOCK.readLock().lock();
			try {
				System.out.println(Thread.currentThread().getName() + "(写锁中的读锁)读取 { " + key + " }");
				Object o = map.get(key);
				System.out.println(Thread.currentThread().getName() + "(写锁中的读锁)结果是 { " + o + " }" + " 读取完成");
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				// 释放 读锁
				REENTRANT_READ_WRITE_LOCK.readLock().unlock();
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			// 写 解锁
			REENTRANT_READ_WRITE_LOCK.writeLock().unlock();
		}
	}
	
	/**
	 * 根据 键值 查找 数值
	 *
	 * @param key 键值
	 */
	public void get(String key) {
		// 读 加锁
		REENTRANT_READ_WRITE_LOCK.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + " 读取 { " + key + " }");
			Object o = map.get(key);
			System.out.println(Thread.currentThread().getName() + " 结果是 { " + o + " }" + " 读取完成");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			// 读 解锁
			REENTRANT_READ_WRITE_LOCK.readLock().unlock();
		}
	}
	
	/**
	 * 根据 键值 查找 数值(读锁的尝试经历)
	 *
	 * @param key    键值
	 * @param newKey 写入的数据的键值
	 * @param value  数值
	 */
	public void get(String key, String newKey, Object value) {
		// 读 加锁
		REENTRANT_READ_WRITE_LOCK.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + " 读取 { " + key + " }");
			Object o = map.get(key);
			System.out.println(Thread.currentThread().getName() + " 结果是 { " + o + " }" + " 读取完成");
			// 获取写锁
			REENTRANT_READ_WRITE_LOCK.writeLock().lock();
			try {
				System.out.println(Thread.currentThread().getName() + "(读锁中的写锁)写入 { " + key + ", " + value + " }");
				map.put(key, value);
				System.out.println(Thread.currentThread().getName() + "(读锁中的写锁)写入完成");
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				// 释放写锁
				REENTRANT_READ_WRITE_LOCK.writeLock().unlock();
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			// 读 解锁
			REENTRANT_READ_WRITE_LOCK.readLock().unlock();
		}
	}

}

/**
 * 实现线程工厂 ThreadFactory
 * 

* 线程工厂的作用:创建一个线程 */ class TestThreadPollFactory implements ThreadFactory { /** * 线程名称前缀 */ private final String threadNamePrefix; /** * 整数,用来标记线程 */ private final AtomicInteger threadIdx = new AtomicInteger(0); public TestThreadPollFactory(String prefix) { threadNamePrefix = prefix; } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); // 拼接结果 -> 线程工厂 测试,线程:n thread.setName(threadNamePrefix + "线程:" + threadIdx.getAndIncrement()); return thread; } }

volatile 关键字

  • volatile 是 JVM 提供的轻量级的同步机制

  • volatile 关键字可以保证并发编程三大特征中的可见性和有序性

  • volatile 关键字只能修饰 属性

比较

ReentrantLock 和 synchronized 的比较

  • ReentrantLock 可以调用线程的 interrupt 方法来中断获取锁等待。而,synchronized 不能中断,只能等待同步代码块执行结束

  • ReentrantLock 可以调用本身 tryLock() 方法来获取锁,超时后自行结束锁的获取。而,synchronized 没有这种机制

  • synchronized 只是公平锁。而,ReentrantLock 有公平锁和非公平锁两种形式

  • synchronized 只是一个 waitSet(等待线程集合)用来存储等待中的线程,不能灵活的唤醒指定的线程,只能按照顺序唤醒。
    而,ReentrantLock 有多个类似于 waitSet(等待线程集合)的变量,通过存储在不同的 waitSet 中,实现灵活的唤醒指定的线程

你可能感兴趣的:(Java,学习笔记,java,jvm,面试)