锁机制:用来保证 在多线程并发情况下数据的一致性
。
锁的作用点:操作一个对象或者调用一个方法前加锁,这样当其他线程也对该对象和方法进行访问时就需要获得锁,如果该锁被其他线程持有,那么该线程则进入阻塞队列等待获得锁
。
Java 中,用作锁的对象有:synchronized、ReentrantLock、ReadWriteLock、volatile。
并发编程三大特征:原子性、可见性、有序性
。
synchronized,中文意思:同步的,也被称为:同步锁
。
synchronized 锁是 Java 中解决并发问题的一种最常用的方法,也是最简单的一种方法
。
synchronized 关键字只能修饰 方法
和 代码块
。
synchronized 锁不能中断,只能等待同步代码块的结束
。
原子性
:确保线程互斥地访问同步代码。
独占锁
。可见性
:保证共享变量的修改能够及时可见。
在 Java 内存模型中,对一个变量 unlock(解锁)操作之前,必须要同步到主内存中
。
如果对一个变量进行 lock(加锁)操作,则将会清空工作内存中此变量的值
。
故,在执行引擎使用此变量前,需要重新从主内存中 load 操作或 assign 操作初始化变量值 来保证的变量的修改能够及时可见
。
有序性
:有效解决重排序问题(处理器为提高运算速度而做出违背代码原有顺序的优化,在正常情况下是不对结果造成影响的)。
但是,锁的 unlock 操作是不能先于 lock 操作发生的。
重排序需要遵守 as-if-serial 规则和 happens-before 规则,目的是为了提高性能。
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 锁的线程安全的语义实现最终依赖的都是 monitor,它才是真正意义上的锁
。
synchronized 正是对 Monitor 机制的一种实现
。
在 Java 中,每一个对象都会关联一个监视器(monitor)
。
monitor:监视器、管程
。
Monitor 作为一种同步机制,它并非 Java 所特有,但 Java 实现了这一机制(synchronized)
。
同一时刻,只有一个 进程/线程 能进入 monitor 中定义的临界区
。
这个临界区是 synchronized 关键字最终所指点的,不管是方法还是代码块。被 synchronized 关键字修饰的方法、代码块,就是 monitor 机制的临界区
。
monitor 机制需要几个元素来配合,分别是:
临界区
monitor 对象及锁
条件变量以及定义在 monitor 对象上的 wait,signal 操作
互斥
:每次只允许一个线程进入临界区
。
协作
:当临界区的线程执行结束后满足特定条件时,可以通知其他的等待线程进入
。
第一步:将 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 指令:线程尝试去获取 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,中文意思:可重入的,同 synchronized 一样,都是可重入锁
。
ReentrantLock 锁是可以中断的,调用线程的 interrupt 方法来中断等待,继续执行下面的代码
。
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(); 创建非公平锁对象
。
方法名 | 作用 |
---|---|
lock() | 用普通方式获取锁,不能中断,没有获取到就直接结束 |
tryLock() | 定时的获取锁,不能中断,没有获取到就等待,超过设定的时间后就不在等待,还没有获取到就结束(没有参数时,与普通方法无异;传入时间和时间单位时,先等待再结束) |
lockInterruptibly() | 可中断的获取锁,可以中断 |
unlock() | 释放所占有的锁 |
newCondition() | 新建一个线程等待区 |
内层方法有权利获得外层方法所占有的锁
。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.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.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("主线程 释放锁");
}
}
}
是指:支持多个自定义的线程集合变量,类似于 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();
}
}
下面的例子中: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 提供了:readLock
和 writeLock
两种锁的操作机制。
读锁: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 是 JVM 提供的轻量级的同步机制
。
volatile 关键字可以保证并发编程三大特征中的可见性和有序性
。
volatile 关键字只能修饰 属性
。
ReentrantLock 可以调用线程的 interrupt 方法来中断获取锁等待
。而,synchronized 不能中断,只能等待同步代码块执行结束
。
ReentrantLock 可以调用本身 tryLock() 方法来获取锁,超时后自行结束锁的获取
。而,synchronized 没有这种机制
。
synchronized 只是公平锁
。而,ReentrantLock 有公平锁和非公平锁两种形式
。
synchronized 只是一个 waitSet(等待线程集合)用来存储等待中的线程,不能灵活的唤醒指定的线程,只能按照顺序唤醒。
而,ReentrantLock 有多个类似于 waitSet(等待线程集合)的变量,通过存储在不同的 waitSet 中,实现灵活的唤醒指定的线程
。