1.1 中断方法
在独占锁加锁过程中,我们看到,线程进入sync queue
中后便调用park()
方法将自己挂起。等待其他线程调用unpark()
方法唤醒自己。那么当我们调用interrupt()
方法时,是否可以中断被操作系统挂起的线程呢?
public class ParkDemo {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
System.out.println("park 开始");
LockSupport.park();
System.out.println("park 结束");
});
//线程挂起
t1.start();
//中断t1
t1.interrupt();
}
}
实际上,park()
的线程实际上会被“唤醒”。
park 开始
park 结束
总结:无论是interrupt()
方法还是unpark()
方法,均是将线程唤醒。线程依赖其他标识位
来判断它是被正常唤醒还是中断唤醒的。
1.2 lock方法中调用中断方法
下列代码的流程:
- t1线程先获取锁,并且持有2s的时间;
- t2线程未获取锁,会调用
park()
方法自我阻塞; - 主线程调用t2线程的
interrupted()
方法中断线程;
@Slf4j
public class Demo {
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
/**
* 开启线程,并获取锁,线程1持有锁
*/
Thread t1 = new Thread(() -> {
reentrantLock.lock();
log.info("t1:打印数据");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock.unlock();
});
t1.start();
Thread.sleep(100);
Thread t2 = new Thread(() -> {
//线程加入到sync queue中,即线程被阻塞
reentrantLock.lock();
log.info("t2:打印数据");
if(Thread.interrupted()){
log.warn("t2:线程被中断过");
}
reentrantLock.unlock();
});
//t2开启线程
t2.start();
Thread.sleep(100);
t2.interrupt();
log.info("主线程");
}
}
执行结果:
13:05:35.882 [Thread-0] INFO com.tellme.lock.conditionLock.Demo - t1:打印数据
13:05:36.071 [main] INFO com.tellme.lock.conditionLock.Demo - 主线程
13:05:37.933 [Thread-1] INFO com.tellme.lock.conditionLock.Demo - t2:打印数据
13:05:37.934 [Thread-1] WARN com.tellme.lock.conditionLock.Demo - t2:线程被中断过
- t2线程被唤醒(中断)之后,它会再次争夺独占锁,若争夺失败,会再次被阻塞。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//自旋再次获取锁,
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
//注意,获取到锁后,最终结果返回的是`中断标识`!
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //我们在这,线程被唤醒
//中断标识修改为true。
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//若是最终返回true,则修改中断标识。
/**
** static void selfInterrupt() {
** Thread.currentThread().interrupt();
** }
**/
selfInterrupt();
}
结论:若调用lock方法进行加锁,若线程被阻塞后,调用interrupt()
中断线程,线程会立即被唤醒,但是唤醒之后会再去争夺锁,若获取锁失败,该线程依旧被阻塞。用户可以在方法内部通过使用if(Thread.interrupted())
判断线程是否被中断过。
1.3 lockInterruptibly方法
@Slf4j
public class Demo {
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
/**
* 开启线程,并获取锁,线程1持有锁
*/
Thread t1 = new Thread(() -> {
reentrantLock.lock();
log.info("t1:打印数据");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock.unlock();
});
t1.start();
Thread.sleep(100);
Thread t2 = new Thread(() -> {
//线程加入到sync queue中,即线程被阻塞
try {
reentrantLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("t2:打印数据");
if(Thread.interrupted()){
log.warn("t2:线程被中断过");
}
reentrantLock.unlock();
});
//t2开启线程
t2.start();
Thread.sleep(100);
t2.interrupt();
log.info("主线程");
}
}
执行结果:
13:16:58.585 [Thread-0] INFO com.tellme.lock.conditionLock.Demo - t1:打印数据
13:16:58.782 [main] INFO com.tellme.lock.conditionLock.Demo - 主线程
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 com.tellme.lock.conditionLock.Demo.lambda$main$1(Demo.java:35)
at java.lang.Thread.run(Thread.java:748)
13:16:58.786 [Thread-1] INFO com.tellme.lock.conditionLock.Demo - t2:打印数据
我们可以看到,t2线程调用park()
方法,被操作系统挂起。之后主线程调用t2.interrupt();
后,t2线程抛出异常,真正中断线程。
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //我们在这被唤醒
//唤醒之后,直接抛出异常。
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
相关阅读
JAVA并发(1)—java对象布局
JAVA并发(2)—PV机制与monitor(管程)机制
JAVA并发(3)—线程运行时发生GC,会回收ThreadLocal弱引用的key吗?
JAVA并发(4)— ThreadLocal源码角度分析是否真正能造成内存溢出!
JAVA并发(5)— 多线程顺序的打印出A,B,C(线程间的协作)
JAVA并发(6)— AQS源码解析(独占锁-加锁过程)
JAVA并发(7)—AQS源码解析(独占锁-解锁过程)
JAVA并发(8)—AQS公平锁为什么会比非公平锁效率低(源码分析)
JAVA并发(9)— 共享锁的获取与释放
JAVA并发(10)—interrupt唤醒挂起线程
JAVA并发(11)—AQS源码Condition阻塞和唤醒
JAVA并发(12)— Lock实现生产者消费者