线程中断机制

概念

java提供了一种用于停止线程的协商机制-中断。称为中断标识协商机制。

常用API

  • public void interrupt()
    仅仅让线程的中断标志位设置为true。不进行其他操作。
  • public boolean isInterrupted()
    获取中断标志位的状态。
  • public static boolean interrupted()
    获取中断标志位的状态。并将中断标志位设置为false

如何停止中断运行的线程

volatile变量实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static volatile boolean isStop = false;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (isStop) {
System.out.println(Thread.currentThread().getName() + "线程isStop = true,自己退出");
break;
}
System.out.println("-------hello interrupt--------");
}
}, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
isStop = true;
}

AtomicBoolean实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static final AtomicBoolean atomicBoolean = new AtomicBoolean(true);

public static void main(String[] args) {
new Thread(() -> {
while (atomicBoolean.get()) {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-------hello------");
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicBoolean.set(false);
}

中断API interrupt和isInterrupted

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("-----t1 线程被中断了,程序结束");
break;
}
System.out.println("-----hello-------");
}
}, "t1");
t1.start();
System.out.println("t1是否被中断:" + t1.isInterrupted());
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
System.out.println("t1是否被中断:" + t1.isInterrupted());
}

注意

  • 如果线程处于正常活动状态,interrupt会将该线程中断状态位设置为true。要想该线程进行进一步处理需要自己根据中断状态为来写业务逻辑。
  • 如果线程处于阻塞状态(sleep, wait, join)在别的线程调用当前线程interrupt方法,那么线程立即退出阻塞状态,并抛出InterruptException异常,并将中断标志为清除(置为false)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    Thread t1 = new Thread(() -> {
    while (true)
    {
    if(Thread.currentThread().isInterrupted())
    {
    System.out.println(Thread.currentThread().getName()+"\t " +
    "中断标志位:"+Thread.currentThread().isInterrupted()+" 程序停止");
    break;
    }

    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    Thread.currentThread().interrupt();//为什么要在异常处,再调用一次??
    // 阻塞状态下的线程设置中断标志位为true,会报异常。中断状态位置为false。导致死循环。因此需要再次设置为true
    e.printStackTrace();
    }

    System.out.println("-----hello InterruptDemo3");
    }
    }, "t1");
    t1.start();

    //暂停几秒钟线程
    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

    new Thread(() -> t1.interrupt(),"t2").start();

拓展:线程有哪些状态?7种状态
创建,就绪(等待CPU),运行,阻塞(等待锁对象),等待(等待事件),超时等待,结束
sleep不会释放锁。wait会释放锁。因此sleep进入等待状态。wait进入阻塞状态。

LockSupport

LockSupport是线程阻塞和唤醒的工具类。主要通过park阻塞和unpark唤醒。

线程等待唤醒机制

Synchronized锁对象的wait和notify

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Object objectLock = new Object();
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLock){
System.out.println(Thread.currentThread().getName()+"\t ----come in");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t ----被唤醒");
}
},"t1").start();
//暂停几秒钟线程
//try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
}
},"t2").start();

限制:

  • 必须在Synchronized同步块中
  • wait必须在之前notify。否则通知唤醒会失效。

Lock.condition的await和signal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
lock.lock();
try
{
System.out.println(Thread.currentThread().getName()+"\t ----come in");
condition.await();
System.out.println(Thread.currentThread().getName()+"\t ----被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"t1").start();
//暂停几秒钟线程
//try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
lock.lock();
try
{
condition.signal();
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
}finally {
lock.unlock();
}
},"t2").start();

LockSupport的park和unpark

1
2
3
4
5
6
7
8
9
10
11
12
13
Thread t1 = new Thread(() -> {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t ----come in"+System.currentTimeMillis());
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t ----被唤醒"+System.currentTimeMillis());
}, "t1");
t1.start();
//暂停几秒钟线程
//try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+"\t ----发出通知");
},"t2").start();

优点:

  • 不需要在锁块中,本身就可以让线程同步。
  • park和unpark不需要有先后顺序。unpark相当于给了park一个凭证。unpark在park执行前也可以让park唤醒。相当于提前给了凭证。(而前面两种就不行)
  • 一个park需要一个凭证。但是不同的unpark作用于一个线程只能给一个凭证。(即是连续调用多次unpark和调用一次作用是一样的)