多线程与并发(四):ReentrantLock
1. 一个例子
一间大屋子有两个功能:睡觉 学习 ,互不干涉
现在小男要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低。解决方法是准备多个房间(多个对像锁)
public class Test1 {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(()->{
bigRoom.study();
},"小男").start();
new Thread(()->{
bigRoom.sleep();
},"小女").start();
}
}
class BigRoom{
public void study(){
synchronized (this){
System.out.println("学习一小时");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void sleep(){
synchronized (this){
System.out.println("睡觉一小时");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
改进
public class Test1 {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(()->{
bigRoom.study();
},"小男").start();
new Thread(()->{
bigRoom.sleep();
},"小女").start();
}
}
class BigRoom{
private final Object studyRoom = new Object();
private final Object sleepRoom = new Object();
public void study(){
synchronized (studyRoom){
System.out.println("学习一小时");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void sleep(){
synchronized (sleepRoom){
System.out.println("睡觉一小时");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
将锁的粒度细分
- 好处,可以增强并发度
- 如果一个线程需要同时获取多把锁,就容易发生死锁
1.1 活跃性
死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
t1 线程获得 A对象锁,接下来想获取B对象锁
t2 线程获得B对象锁,接下来想获取A对象锁
例如:
public class Test2 {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(()->{
synchronized (A){
System.out.println(Thread.currentThread().getName()+"lock A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
System.out.println(Thread.currentThread().getName()+"lock B");
}
}
},"t1");
Thread t2 = new Thread(()->{
synchronized (B){
System.out.println(Thread.currentThread().getName()+"lock B");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A){
System.out.println(Thread.currentThread().getName()+"lock A");
}
}
},"t2");
t1.start();
t2.start();
}
}
结果:
t1lock A
t2lock B
定位死锁
- 检测死锁可以使用jconsole工具,或者使用jps定位进程id,再用jstack 定位死锁。
ItsBadForYadeMacBook-Pro:lily itsbadforya$ jps
26273 Launcher
26275 Test2
25431 Test
25194
26285 Jps
26270 KotlinCompileDaemon
ItsBadForYadeMacBook-Pro:lily itsbadforya$ jstack 26275
2020-10-05 14:39:51
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):
"Attach Listener" #16 daemon prio=9 os_prio=31 tid=0x00007ff49e827000 nid=0x470b waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #15 prio=5 os_prio=31 tid=0x00007ff4a1159800 nid=0xf03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"t2" #14 prio=5 os_prio=31 tid=0x00007ff4a08d6800 nid=0xa403 waiting for monitor entry [0x0000700006a75000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Thread.Test2.lambda$main$1(Test2.java:32)
- waiting to lock <0x00000007957dab48> (a java.lang.Object)
- locked <0x00000007957dab58> (a java.lang.Object)
at Thread.Test2$$Lambda$2/1879492184.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"t1" #13 prio=5 os_prio=31 tid=0x00007ff4a0846000 nid=0x5703 waiting for monitor entry [0x0000700006972000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Thread.Test2.lambda$main$0(Test2.java:18)
- waiting to lock <0x00000007957dab58> (a java.lang.Object)
- locked <0x00000007957dab48> (a java.lang.Object)
at Thread.Test2$$Lambda$1/1674896058.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #12 daemon prio=9 os_prio=31 tid=0x00007ff49e03f000 nid=0xa803 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #11 daemon prio=9 os_prio=31 tid=0x00007ff4a086f000 nid=0x5503 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #10 daemon prio=9 os_prio=31 tid=0x00007ff4a086e000 nid=0x3c03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #9 daemon prio=9 os_prio=31 tid=0x00007ff4a086d000 nid=0x3d03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #8 daemon prio=9 os_prio=31 tid=0x00007ff4a086c800 nid=0x3903 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"JDWP Command Reader" #7 daemon prio=10 os_prio=31 tid=0x00007ff4a380b800 nid=0x3803 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"JDWP Event Helper Thread" #6 daemon prio=10 os_prio=31 tid=0x00007ff49e817800 nid=0x4003 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"JDWP Transport Listener: dt_socket" #5 daemon prio=10 os_prio=31 tid=0x00007ff49e816800 nid=0x3607 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007ff49e810000 nid=0x3503 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007ff4a3808800 nid=0x2f03 in Object.wait() [0x0000700005d4b000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795588ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x0000000795588ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007ff4a0826800 nid=0x4d03 in Object.wait() [0x0000700005c48000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795586bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000795586bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=31 tid=0x00007ff4a081f800 nid=0x4e03 runnable
"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007ff49e00d000 nid=0x2307 runnable
"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007ff49e010000 nid=0x2103 runnable
"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007ff49e010800 nid=0x1e03 runnable
"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007ff49e011800 nid=0x2a03 runnable
"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007ff49e012000 nid=0x5403 runnable
"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007ff49e012800 nid=0x2d03 runnable
"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007ff49e013000 nid=0x5203 runnable
"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007ff49e014000 nid=0x5003 runnable
"VM Periodic Task Thread" os_prio=31 tid=0x00007ff4a38b8000 nid=0x5603 waiting on condition
JNI global references: 2246
Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x00007ff4a082c4a8 (object 0x00000007957dab48, a java.lang.Object),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007ff4a0828408 (object 0x00000007957dab58, a java.lang.Object),
which is held by "t2"
Java stack information for the threads listed above:
===================================================
"t2":
at Thread.Test2.lambda$main$1(Test2.java:32)
- waiting to lock <0x00000007957dab48> (a java.lang.Object)
- locked <0x00000007957dab58> (a java.lang.Object)
at Thread.Test2$$Lambda$2/1879492184.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"t1":
at Thread.Test2.lambda$main$0(Test2.java:18)
- waiting to lock <0x00000007957dab58> (a java.lang.Object)
- locked <0x00000007957dab48> (a java.lang.Object)
at Thread.Test2$$Lambda$1/1674896058.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
ItsBadForYadeMacBook-Pro:lily itsbadforya$
哲学家就餐问题
有五位哲学家,围坐在圆桌旁
-
他们只做两件事,思考和吃饭,思考一会吃口饭,吃完后继续思考
-
吃饭时要用两根筷子吃,桌子上共有五根筷子,每位哲学家左右手边各有一根筷子
-
如果筷子被身边的人拿着,自己就得等着
-
代码
public class Test3 {
public static void main(String[] args) {
//创建五根筷子
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
//创建五个哲学家
new Philosopher("苏格拉底",c1,c2).start();
new Philosopher("柏拉图",c2,c3).start();
new Philosopher("亚里士多德",c3,c4).start();
new Philosopher("赫拉克利特",c4,c5).start();
new Philosopher("阿基米德",c5,c1).start();
}
}
class Philosopher extends Thread{
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left,Chopstick right){
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while(true){
//尝试获得左手的筷子
synchronized (left){
//尝试获得右手的筷子
synchronized (right){
eat();
}
}
}
}
private void eat(){
System.out.println(Thread.currentThread().getName()+"eating...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Chopstick{
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopstick{" +
"name='" + name + '\'' +
'}';
}
}
执行一段时间后出现死锁,问题定位:
ItsBadForYadeMacBook-Pro:lily itsbadforya$ jstack 26610
2020-10-05 20:02:07
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):
"Attach Listener" #19 daemon prio=9 os_prio=31 tid=0x00007f868d027000 nid=0x3707 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #18 prio=5 os_prio=31 tid=0x00007f868d812000 nid=0x1003 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"阿基米德" #17 prio=5 os_prio=31 tid=0x00007f868c048000 nid=0xa103 waiting for monitor entry [0x00007000062fd000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc120> (a Thread.Chopstick)
- locked <0x00000007957dc190> (a Thread.Chopstick)
"赫拉克利特" #16 prio=5 os_prio=31 tid=0x00007f868d811000 nid=0x5a03 waiting for monitor entry [0x00007000061fa000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc190> (a Thread.Chopstick)
- locked <0x00000007957dc180> (a Thread.Chopstick)
"亚里士多德" #15 prio=5 os_prio=31 tid=0x00007f8689839000 nid=0xa403 waiting for monitor entry [0x00007000060f7000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc180> (a Thread.Chopstick)
- locked <0x00000007957dc170> (a Thread.Chopstick)
"柏拉图" #14 prio=5 os_prio=31 tid=0x00007f8688851000 nid=0xa503 waiting for monitor entry [0x0000700005ff4000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc170> (a Thread.Chopstick)
- locked <0x00000007957dc160> (a Thread.Chopstick)
"苏格拉底" #13 prio=5 os_prio=31 tid=0x00007f8688850800 nid=0xa603 waiting for monitor entry [0x0000700005ef1000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc160> (a Thread.Chopstick)
- locked <0x00000007957dc120> (a Thread.Chopstick)
"Service Thread" #12 daemon prio=9 os_prio=31 tid=0x00007f868784d000 nid=0xa903 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #11 daemon prio=9 os_prio=31 tid=0x00007f868d808800 nid=0x5503 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #10 daemon prio=9 os_prio=31 tid=0x00007f8688852000 nid=0x4103 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #9 daemon prio=9 os_prio=31 tid=0x00007f868884f800 nid=0x3f03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #8 daemon prio=9 os_prio=31 tid=0x00007f868884f000 nid=0x3d03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"JDWP Command Reader" #7 daemon prio=10 os_prio=31 tid=0x00007f8689833000 nid=0x3c03 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"JDWP Event Helper Thread" #6 daemon prio=10 os_prio=31 tid=0x00007f868d008800 nid=0x4503 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"JDWP Transport Listener: dt_socket" #5 daemon prio=10 os_prio=31 tid=0x00007f8687808800 nid=0x4707 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007f868801a000 nid=0x4803 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007f8689830800 nid=0x4f03 in Object.wait() [0x00007000052ca000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795588ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x0000000795588ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007f8688812800 nid=0x5103 in Object.wait() [0x00007000051c7000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000795586bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000795586bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=31 tid=0x00007f868982f800 nid=0x5303 runnable
"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007f868800b800 nid=0x2307 runnable
"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007f868800f000 nid=0x2103 runnable
"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007f8688010000 nid=0x1e03 runnable
"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007f868880a800 nid=0x2a03 runnable
"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007f8688010800 nid=0x2c03 runnable
"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007f868880b800 nid=0x2e03 runnable
"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007f8688011000 nid=0x3003 runnable
"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007f8688011800 nid=0x5403 runnable
"VM Periodic Task Thread" os_prio=31 tid=0x00007f868c00b000 nid=0x5703 waiting on condition
JNI global references: 1441
Found one Java-level deadlock:
=============================
"阿基米德":
waiting to lock monitor 0x00007f86888540b8 (object 0x00000007957dc120, a Thread.Chopstick),
which is held by "苏格拉底"
"苏格拉底":
waiting to lock monitor 0x00007f86880177f8 (object 0x00000007957dc160, a Thread.Chopstick),
which is held by "柏拉图"
"柏拉图":
waiting to lock monitor 0x00007f8688017748 (object 0x00000007957dc170, a Thread.Chopstick),
which is held by "亚里士多德"
"亚里士多德":
waiting to lock monitor 0x00007f8688854008 (object 0x00000007957dc180, a Thread.Chopstick),
which is held by "赫拉克利特"
"赫拉克利特":
waiting to lock monitor 0x00007f8688853f58 (object 0x00000007957dc190, a Thread.Chopstick),
which is held by "阿基米德"
Java stack information for the threads listed above:
===================================================
"阿基米德":
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc120> (a Thread.Chopstick)
- locked <0x00000007957dc190> (a Thread.Chopstick)
"苏格拉底":
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc160> (a Thread.Chopstick)
- locked <0x00000007957dc120> (a Thread.Chopstick)
"柏拉图":
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc170> (a Thread.Chopstick)
- locked <0x00000007957dc160> (a Thread.Chopstick)
"亚里士多德":
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc180> (a Thread.Chopstick)
- locked <0x00000007957dc170> (a Thread.Chopstick)
"赫拉克利特":
at Thread.Philosopher.run(Test3.java:44)
- waiting to lock <0x00000007957dc190> (a Thread.Chopstick)
- locked <0x00000007957dc180> (a Thread.Chopstick)
Found 1 deadlock.
活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如:
public class Test4 {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
//期望减到0退出循环
while (count > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "current count is " + count);
}
}, "t1").start();
new Thread(() -> {
//期望超过20退出循环
while (count < 20) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName() + "current count is " + count);
}
}, "t2").start();
}
}
执行结果:不能停止
解决方法:睡眠的时间要不一样
饥饿
很多线程中把饥饿定义为,一个线程由于优先级太低,始终得不到CPU调度,也不能够结束,饥饿的情况不易演示,这样读写锁时会涉及饥饿的问题。
下面我讲一个我遇到的线程饥饿的例子,先来看看使用顺序加锁的方式解决之前死锁的问题
死锁.jpg
顺序加锁的解决方案
死锁-2.jpg
没有死锁,但是有可能会造成线程饥饿,得不到执行
2. ReentrantLock
相对于synchronized 它具备如下特点:
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与synchronized 一样都支持可重入
基本语法
//获取锁
reentrantLock.lock();
try{
//临界区
}finally{
//释放锁
reentrantLock.unlock();
}
2.1 可重入
可重入是指同一个线程如果首次获得这把锁,那么因为它是这把锁的拥有者,因此具有权利再次获取这把锁。如果不是可重入锁,那么第二次获得锁的时候,自己会被锁挡住。
public class Test5 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1(){
lock.lock();
try {
System.out.println("excute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2(){
lock.lock();
try {
System.out.println("excute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3(){
lock.lock();
try {
System.out.println("excute method3");
} finally {
lock.unlock();
}
}
}
2.2 可打断
public class Test5 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try {
//如果没有竞争那么此方法就会获取lock对象锁
//如果有竞争就进入阻塞队列,可以被其它线程用interrupt()方法打断
System.out.println("尝试获得锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("没有获得锁,返回");
return;
}
try {
System.out.println("获取到锁");
} finally {
lock.unlock();
}
},"t1");
lock.lock();
t1.start();
try {
Thread.sleep(1000);
System.out.println("打断t1");
t1.interrupt();
} catch(InterruptedException e) {
}
}
执行结果:
尝试获得锁
打断t1
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at Thread.Test5.lambda$main$0(Test5.java:16)
at java.lang.Thread.run(Thread.java:748)
没有获得锁,返回
2.3 锁超时
立刻失败
public class Thread5 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
System.out.println("启动");
System.out.println("尝试获取锁");
if (!lock.tryLock()) {
System.out.println("获取立刻失败,返回");
return;
}
try {
System.out.println(Thread.currentThread().getName() + "获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得了锁");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
执行结果:
main获得了锁
启动
尝试获取锁
获取立刻失败,返回
带参数的tryLock(long n,TimeUnit unit)
public class Thread5 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
System.out.println("启动");
System.out.println("尝试获取锁");
try {
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
System.out.println("获取立刻失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName() + "获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得了锁");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
执行结果:
main获得了锁
启动
尝试获取锁
t1获得了锁
2.4 锁超时-解决哲学家就餐问题
public class Test3 {
public static void main(String[] args) {
//创建五根筷子
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
//创建五个哲学家
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
//尝试获得左手的筷子
if (left.tryLock()) {
try{
//尝试获得右手的筷子
if (right.tryLock()){
try{
eat();
}finally {
right.unlock();
}
}
}finally {
left.unlock();//获取右边筷子失败,释放自己的左手筷子
}
}
}
}
private void eat() {
System.out.println(Thread.currentThread().getName() + "eating...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopstick{" +
"name='" + name + '\'' +
'}';
}
}
2.5 公平锁
ReentrantLock默认是不公平的
- 从源码的角度分析
公平锁一般没有必要,会降低并发度。
2.6 条件变量
synchronized 中也有条件变量,就是我们讲原理的那个waitSet休息室,当条件不满足时进入waitSet等待。
ReentrantLock的条件变量比synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室中等消息
- ReentrantLock 支持多间休息室,有专门等烟的休息室,有专门等外卖的休息室。唤醒时也是按休息室来唤醒。
使用流程
- await前需要获得锁
- await 执行后,会释放锁,进入conditionObject等待
- await 的线程被唤醒(或打断,或超时),重新竞争lock锁
- 竞争lock锁成功后,从await后继续执行
public class Thread6 {
static ReentrantLock Room = new ReentrantLock();
//创建条件变量
static Condition waitCigaretteQueue = Room.newCondition();
static Condition waitBreakfastQueue = Room.newCondition();
static volatile boolean hasCigarette;
static volatile boolean hasBreakfast;
public static void main(String[] args) {
new Thread(() -> {
Room.lock();
try {
System.out.println(Thread.currentThread().getName() + "有烟没?" + hasCigarette);
while (!hasCigarette) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "再次问有烟没?" + hasCigarette);
if (hasCigarette) {
System.out.println(Thread.currentThread().getName() + "可以开始干活了");
} else {
System.out.println("没干成");
}
} finally {
Room.unlock();
}
}, "小男").start();
new Thread(() -> {
Room.lock();
try {
System.out.println(Thread.currentThread().getName() + "有早餐没?" + hasBreakfast);
while (!hasBreakfast) {
try {
waitBreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "再次问有早餐没?" + hasBreakfast);
if (hasBreakfast) {
System.out.println(Thread.currentThread().getName() + "可以开始干活了");
} else {
System.out.println("没干成");
}
} finally {
Room.unlock();
}
}, "小女").start();
new Thread(() -> {
Room.lock();
try {
System.out.println("送外卖来了");
hasBreakfast = true;
waitBreakfastQueue.signal();
} finally {
Room.unlock();
}
}, "送外卖的").start();
new Thread(() -> {
Room.lock();
try {
System.out.println("送烟来了");
hasCigarette = true;
waitCigaretteQueue.signal();
} finally {
Room.unlock();
}
}, "送烟的").start();
}
}
执行结果:
小男有烟没?false
小女有早餐没?false
送外卖来了
小女再次问有早餐没?true
小女可以开始干活了
送烟来了
小男再次问有烟没?true
小男可以开始干活了
同步模式之顺序控制
比如,必须先2后1打印
wait-notify
public class Test6 {
static Object obj = new Object();
//t2运行标记,代表t2是否执行过
static boolean t2runed = false;
public static void main(String[] args) {
Thread t1 = new Thread(()->{
synchronized (obj){
while (!t2runed){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("打印1");
}
},"t1");
Thread t2 = new Thread(()->{
synchronized (obj){
System.out.println("打印2");
t2runed = true;
obj.notify();
}
},"t2");
t1.start();
t2.start();
}
}
执行结果:
打印2
打印1
park & unpark解决方式
public class Test7 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
LockSupport.park();
System.out.println("打印1");
}, "t1");
Thread t2 = new Thread(() -> {
System.out.println("打印2");
LockSupport.unpark(t1);
}, "t2");
t1.start();
t2.start();
}
}
执行结果:
打印2
打印1
交替输出
线程1 输出a5次,线程2输出b 5次,线程3输出c 5次。要求输出abcabcabcabcabc
wait - notify版本
public class Test8 {
public static void main(String[] args) {
WaitNotify waitNotify = new WaitNotify(1,5);
new Thread(()->{
waitNotify.print("a",1,2);
}).start();
new Thread(()->{
waitNotify.print("b",2,3);
}).start();
new Thread(()->{
waitNotify.print("c",3,1);
}).start();
}
}
class WaitNotify{
//等待标记
private int flags;
//循环次数
private int loopNumber;
public WaitNotify(int flags, int loopNumber){
this.flags = flags;
this.loopNumber = loopNumber;
}
public void print(String str, int waitFlags, int nextFlags){
for (int i = 0; i < loopNumber; i++) {
synchronized (this){
while (flags!= waitFlags){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
flags = nextFlags;
this.notifyAll();
}
}
}
}
ReentrantLock 方式
public class Test9 {
public static void main(String[] args) {
AwaitSignal awaitSignal = new AwaitSignal(5);
Condition a = awaitSignal.newCondition();
Condition b = awaitSignal.newCondition();
Condition c = awaitSignal.newCondition();
new Thread(() -> {
awaitSignal.print("a", a, b);
}).start();
new Thread(() -> {
awaitSignal.print("b", b, c);
}).start();
new Thread(() -> {
awaitSignal.print("c", c, a);
}).start();
//所有的线程先进入休息室,然后由主线程唤醒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
awaitSignal.lock();
try{
a.signal();
}finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock {
private int loopNumber;
public AwaitSignal(int i) {
loopNumber = i;
}
//参数一:打印内容,参数二:进入哪一间休息室等待,参数三:下一间休息室
public void print(String str, Condition current, Condition next) {
for (int i = 0; i < loopNumber; i++) {
lock();
try {
current.await();
System.out.print(str);
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
}
park&unpark方式
public class Test10 {
private static Thread t1, t2, t3;
public static void main(String[] args) {
ParkUnpark parkUnpark = new ParkUnpark(5);
t1 = new Thread(() -> {
parkUnpark.print("a", t2);
});
t2 = new Thread(() -> {
parkUnpark.print("b", t3);
});
t3 = new Thread(() -> {
parkUnpark.print("c", t1);
});
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}
}
class ParkUnpark {
private int loopNumber;
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str, Thread next) {
for (int i = 0; i < loopNumber; i++) {
LockSupport.park();
System.out.print(str);
LockSupport.unpark(next);
}
}
}