我的原则:先会用再说,内部慢慢来
一、 概念
属于可重入锁:可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。上一章节有讲到。
二、 ReentrantLock 可重入
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class _08_01_TestReentrantLock {
Lock lock = new ReentrantLock();
public void print() throws InterruptedException {
System.out.println("do print");
lock.lock();
doAdd();
System.out.println("unlock do print");
lock.unlock();
}
public void doAdd() throws InterruptedException {
System.out.println("do add");
lock.lock();
System.out.println("unlock do add");
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
_08_01_TestReentrantLock testLock = new _08_01_TestReentrantLock();
testLock.print();
}
}
输出:
do print
do add
unlock do add
unlock do print
讲解: 同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响
三、 lock 与 unlock
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class _09_TestReentrantLock {
public static void main(String[] args){
Thread t1 = new Thread(new MyThread_Interrupted(),"AAAAAA");
Thread t2 = new Thread(new MyThread_Interrupted(),"BBBBBB");
t1.start();
t2.start();
// t2.interrupt();
}
}
class MyThread_Interrupted implements Runnable{
private static Lock lock = new ReentrantLock();
public void run(){
try{
System.out.println(Thread.currentThread().getName() + " wait to lock .");
lock.lock(); //中断继续等待
// lock.lockInterruptibly(); //中断直接抛出异常
System.out.println(Thread.currentThread().getName() + " get the lock and running .");
TimeUnit.SECONDS.sleep(5);
}
catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted .");
} finally {
System.out.println(Thread.currentThread().getName() + " finished . unlock ");
lock.unlock();
}
}
}
输出:
BBBBBB wait to lock .
AAAAAA wait to lock .
BBBBBB get the lock and running .
BBBBBB finished . unlock
AAAAAA get the lock and running .
AAAAAA finished . unlock
结论:从结果上看,B 线程先拿到lock,然后A一直等待,知道B完成unlock,A才拿到锁
四、 lock.lock() 与 lock.lockinterruptibly()
- lock.lock() 等待拿到锁,打断我也继续等,等到天荒地老。
- lock.lockinterruptibly() 接受你的打断,然后抛异常。不等锁了
重点:下面模拟一种情况: B 拿到锁,一直没释放,A 在等待获取锁的过程中,遇到 Interrupted 的时候,会作出什么反应。这也是 lock.lock() 与 lock.lockinterruptibly() 的区别。
代码:
public class _09_TestReentrantLock {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new MyThread_Interrupted(), "AAAAAA");
Thread t2 = new Thread(new MyThread_Interrupted(), "BBBBBB");
// 1. B 线程先跑起来
t2.start();
// sleep 确保 B 线程先走 ,A 线程再启动
TimeUnit.SECONDS.sleep(1);
// 2. A 在 B 线程后面跑起来
t1.start();
// sleep 确保先 lock 操作,再来打断
TimeUnit.SECONDS.sleep(1);
/*
3. A 线程现在依旧在等待,然后这个时候打断。
lock.lock() 与 lock.lockInterruptibly() 的处理方式不同
*/
t1.interrupt();
System.out.println(" t1.interrupt() OK ...");
}
}
class MyThread_Interrupted implements Runnable {
private static ReentrantLock lock = new ReentrantLock();
private boolean hasLock = false;
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " wait to lock .");
lock.lock(); //被中断,继续等待
// lock.lockInterruptibly(); //被中断,直接抛出异常
hasLock = true;
System.out.println(Thread.currentThread().getName() + " get the lock and running .");
// 重点:这个操作是模拟B线程一直在处理没能释放锁,为的是让A一直等待着拿锁。
if (Thread.currentThread().getName().equals("BBBBBB")) {
Scanner sc = new Scanner(System.in);
System.out.println("点击任意键终止线程 ...");
sc.nextLine();
}
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " be interrupted .");
} finally {
System.out.println(Thread.currentThread().getName() + ", has lock -> " + hasLock);
// 如果没获取到 lock ,然后去 unlock ,会跑出 IllegalMonitorStateException 异常
if(hasLock){
System.out.println(Thread.currentThread().getName() + " begin to unlock .");
lock.unlock();
System.out.println(Thread.currentThread().getName() + " finished . unlock ");
}
}
}
}
- lock.lock() 的输出:
BBBBBB wait to lock .
BBBBBB get the lock and running .
点击任意键终止线程 ...
AAAAAA wait to lock .
t1.interrupt() OK ...
// 这里输入任意键,目的是让 B线程往下走,然后释放锁
BBBBBB, has lock -> true
BBBBBB begin to unlock .
BBBBBB finished . unlock
AAAAAA get the lock and running .
AAAAAA, has lock -> true
AAAAAA begin to unlock .
AAAAAA finished . unlock
- lock.lockInterruptibly() 的输出:
BBBBBB wait to lock .
BBBBBB get the lock and running .
点击任意键终止线程 ...
AAAAAA wait to lock .
t1.interrupt() OK ...
AAAAAA be interrupted .
AAAAAA, has lock -> false
// 这里输入任意键,目的是让 B线程往下走,然后释放锁
BBBBBB, has lock -> true
BBBBBB begin to unlock .
BBBBBB finished . unlock
结论:
3. lock.lock() 忽略 interrupt() 操作,继续阻塞等待获取 lock .
4. lock.lockinterruptibly() 接受打断,不去拿锁了,直接抛 Exception
五、 trylock
尝试获取锁,拿不到就直接返回 false
- boolean ReentrantLock#tryLock() :直接尝试获取锁,返回true或者false
- boolean ReentrantLock#tryLock(long timeout, TimeUnit unit):直接尝试获取锁,获取不到重试一段时间。
六、 条件变量Condition 的使用
java.util.concurrent.locks.Condition
满足某个条件Condition:线程可以跑,不满足的话,线程挂起。
思考这么一道题:编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
public class _12_TestABCAlternate_Condition {
public static void main(String[] args) {
AlternateDemo demo = new AlternateDemo();
new Thread(() -> {
for (int i = 1; i < 5; i++) {
demo.LoopA(i);
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i < 5; i++) {
demo.LoopB(i);
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i < 5; i++) {
demo.LoopC(i);
}
}, "C").start();
}
}
class AlternateDemo {
private int number = 1; //当前正在执行线程的标记
private ReentrantLock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
/**
* @param totalLoop : 循环第几轮
*/
public void LoopA(int totalLoop) {
lock.lock();
try {
//1. 判断
if (number != 1) {
condition1.await();
}
//2. 打印
System.out.println(Thread.currentThread().getName() + "-" + totalLoop);
//3. 唤醒
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void LoopB(int totalLoop) {
lock.lock();
try {
if (number != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "-" + totalLoop);
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void LoopC(int totalLoop) {
lock.lock();
try {
if (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "-" + totalLoop);
System.out.println("--------------------------------------------------");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出:
A-1
B-1
C-1
--------------------------------------------------
A-2
B-2
C-2
--------------------------------------------------
A-3
B-3
C-3
--------------------------------------------------
A-4
B-4
C-4
--------------------------------------------------
结论:满足条件:run,不满足条件 blocking