全网最详细并发编程(4)—入门篇
本文主要讲解ReentrantLock、
一、 ReentrantLock
相对于 synchronized 它具备如下特点
● 可中断
● 可以设置超时时间
● 可以设置为公平锁
● 支持多个条件变量
● 与 synchronized 一样,都支持可重入
基本语法
// 获取锁
reentrantLock.lock(); //此行放在try块内和外效果都一样
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁;如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
输出
17:59:11.862 [main] c.TestReentrant - execute method1
17:59:11.865 [main] c.TestReentrant - execute method2
17:59:11.865 [main] c.TestReentrant - execute method3
可打断
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
lock.lockInterruptibly(); //可被打断的锁
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断"); //没有获得锁就被打断跑出的异常
return; //异常返回
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock(); //主线程先获得锁
log.debug("获得了锁");
t1.start();
try {
sleep(1);
t1.interrupt(); //打断t1线程
log.debug("执行打断");
} finally {
lock.unlock();
}
输出
18:02:40.520 [main] c.TestInterrupt - 获得了锁
18:02:40.524 [t1] c.TestInterrupt - 启动...
18:02:41.530 [main] c.TestInterrupt - 执行打断
java.lang.InterruptedException
at
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchr
onizer.java:898)
at
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchron
izer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at cn.itcast.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17)
at java.lang.Thread.run(Thread.java:748)
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断
注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
lock.lock(); //可被打断的锁
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断"); //没有获得锁就被打断跑出的异常
return; //异常返回
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock(); //主线程先获得锁
log.debug("获得了锁");
t1.start();
try {
sleep(1);
t1.interrupt(); //打断t1线程
log.debug("执行打断");
} finally {
log.debug("释放了锁")
lock.unlock();
}
输出
18:06:56.261 [main] c.TestInterrupt - 获得了锁
18:06:56.265 [t1] c.TestInterrupt - 启动...
18:06:57.266 [main] c.TestInterrupt - 执行打断 // 这时 t1 并没有被真正打断, 而是仍继续等待锁
18:06:58.267 [main] c.TestInterrupt - 释放了锁
18:06:58.267 [t1] c.TestInterrupt - 获得了锁
锁超时
(1) 立刻失败
import lombok.extern.slf4j.Slf4j;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test22")
public class Test22 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
try {
if (! lock.tryLock()) {
log.debug("获取立刻失败,返回");
return;
}
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得到锁");
t1.start();
sleep(2);
log.debug("释放了锁");
lock.unlock();
}
}
输出
18:15:02.918 [main] c.TestTimeout - 获得了锁
18:15:02.921 [t1] c.TestTimeout - 启动...
18:15:02.921 [t1] c.TestTimeout - 获取立刻失败,返回
(2) 超时失败
import lombok.extern.slf4j.Slf4j;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test22")
public class Test22 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
try {
if (! lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("获取等待1s后失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("获取不到锁");
return;
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得到锁");
t1.start();
log.debug("释放了锁");
lock.unlock();
}
}
输出
18:19:40.537 [main] c.TestTimeout - 获得了锁
18:19:40.544 [t1] c.TestTimeout - 启动...
18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回
(3)锁超时解决哲学家就餐问题
import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test23")
public class Test23 {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();
}
}
@Slf4j(topic = "c.Philosopher")
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() {
log.debug("eating...");
Sleeper.sleep(0.5);
}
}
class Chopstick extends ReentrantLock { //继承ReentrantLock
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
(4) ReentrantLock 条件变量Condition的使用
@Slf4j(topic = "c.Test24")
public class Test24 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
static ReentrantLock ROOM = new ReentrantLock();
// 等待烟的休息室
static Condition waitCigaretteSet = ROOM.newCondition();
// 等外卖的休息室
static Condition waitTakeoutSet = ROOM.newCondition();
public static void main(String[] args) {
new Thread(() -> {
ROOM.lock();
try {
log.debug("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
waitCigaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小南").start();
new Thread(() -> {
ROOM.lock();
try {
log.debug("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小女").start();
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasTakeout = true;
waitTakeoutSet.signal();
} finally {
ROOM.unlock();
}
}, "送外卖的").start();
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasCigarette = true;
waitCigaretteSet.signal();
} finally {
ROOM.unlock();
}
}, "送烟的").start();
}
}
二、设计模式
(1)固定顺序输出
① 使用wait/notify
@Slf4j(topic = "c.Test25")
public class Test25 {
static final Object lock = new Object();
// 表示 t2 是否运行过
static boolean t2runned = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
while (!t2runned) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lock) {
log.debug("2");
t2runned = true;
lock.notify();
}
}, "t2");
t1.start();
t2.start();
}
}
② 使用park/unpark(简洁,没有锁的概念,以线程为单位,唤醒线程)
@Slf4j(topic = "c.Test26")
public class Test26 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
LockSupport.park();
log.debug("1");
}, "t1");
t1.start();
new Thread(() -> {
log.debug("2");
LockSupport.unpark(t1);
},"t2").start();
}
}
③ 使用ReentrantLock
@Slf4j(topic = "c.TestReentrantLock")
public class TestReentrantLock {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static boolean t2runned = false;
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
lock.lock();
try {
while (!t2runned){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("我是t1");
}finally {
lock.unlock();
}
},"t1");
Thread t2 = new Thread(() ->{
lock.lock();
try {
log.debug("我是t2");
t2runned = true;
condition.signal();
}finally {
lock.unlock();
}
},"t2");
t1.start();
t2.start();
}
}
(2)交替输出
① 使用wait/notify
public class TestWaitNotify {
public static void main(String[] args) {
WaitNotify waitNotify = new WaitNotify(1,5);
new Thread(() -> {
waitNotify.print("a",1,2);
},"a线程").start();
new Thread(() -> {
waitNotify.print("b",2,3);
},"b线程").start();
new Thread(() -> {
waitNotify.print("c",3,1);
},"c线程").start();
}
}
class WaitNotify {
private int flag;
private int loopNumber;
/*打印*/
public void print(String str ,int waitFlag , int nextFlag){
for (int i = 0; i < loopNumber; i++) {
synchronized (this){
while (waitFlag != this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
this.flag = nextFlag;
this.notifyAll();
}
}
}
public WaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
public int getFlag() {
return flag;
}
public void setFlag(int flag) {
this.flag = flag;
}
public int getLoopNumber() {
return loopNumber;
}
public void setLoopNumber(int loopNumber) {
this.loopNumber = loopNumber;
}
}
② 使用park/unpark(简洁,没有锁的概念,以线程为单位,唤醒线程)
public class TestParkUnpark {
static Thread a ;
static Thread b ;
static Thread c ;
public static void main(String[] args) {
ParkUnpark parkUnpark = new ParkUnpark(5);
a = new Thread(() -> {
parkUnpark.print("a",b);
},"a");
b = new Thread(() -> {
parkUnpark.print("b",c);
},"b");
c = new Thread(() -> {
parkUnpark.print("c",a);
},"c");
a.start();
b.start();
c.start();
LockSupport.unpark(a);
}
}
class ParkUnpark {
private int loopNumber;
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str, Thread nextThread){
for (int i = 0; i < loopNumber; i++) {
LockSupport.park();
System.out.print(str);
LockSupport.unpark(nextThread);
}
}
}
③ 使用ReentrantLock (休息室Condition)
public class TestAwaitSignal {
public static void main(String[] args) throws InterruptedException {
AwaitSignal awaitSignal = new AwaitSignal(5);
Condition a_condition = awaitSignal.newCondition();
Condition b_condition = awaitSignal.newCondition();
Condition c_condition = awaitSignal.newCondition();
new Thread(() -> {
awaitSignal.print("a",a_condition,b_condition);
},"a").start();
new Thread(() -> {
awaitSignal.print("b",b_condition,c_condition);
},"b").start();
new Thread(() -> {
awaitSignal.print("c",c_condition,a_condition);
},"c").start();
Thread.sleep(1000);
System.out.println("==========开始=========");
awaitSignal.lock();
try {
a_condition.signal(); //首先唤醒a线程
}finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock {
private int loopNumber;
public AwaitSignal(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str, Condition condition, Condition next){
for (int i = 0; i < loopNumber; i++) {
lock();
try{
try {
condition.await();
// System.out.print("i:==="+i);
System.out.print(str);
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
unlock();
}
}
}
}
三、入门篇小结
欢迎关注公众号Java技术大本营,会不定期分享BAT面试资料等福利。