线程简介
- 进程与线程
进程是操作系统中进行保护和资源分配的基本单位,操作系统分配资源以进程为基本单位。而线程是进程的组成部分,它代表了一条顺序的执行流。
-
线程的状态
- NEW 线程刚被创建
- RUNNABLE 可运行状态。包括正在运行中和等待CPU分配时间片段
- BLOCKED 阻塞状态。正在等待获取synchronized监视器
- WAITING 等待状态。进入此状态的方法有:wait(),join(),park()
- TIMED_WAITING 有超时时间限制的等待。sleep(),wait(long),join(long),LockSupport.parkNanos(long),LockSupport.parkUntil(long)
- TERMINATED 线程执行结束,被终止
- 创建线程
- 继承Thread类,重写run()方法。
- 实现Runnable接口,重写run()方法。
- 需要注意的问题
- 线程并不是越多越好,线程的切换是需要消耗资源的,这是上下文切换的成本。
- 死锁。线程之间互相等待对方释放锁,会出现死锁的情况。
- 多线程并发。
- 线程生命周期
- 解决并发问题的常见锁
- synchronized
- ReentrantLock
- CountDownLatch
具体的比较分析,其他章节再做讨论。
线程通信
线程通信的方式有以下几种:
1、 JDK中提供的wait(),notify(),notifyAll()
2、 ReentrantLock中使用的Condition
3、 LockSupport
- JDK中提供的wait(),notify(),notifyAll()
- wait():使线程进入WAITING状态,只有等待另一个线程 通知或者被中断才返回。
- notify():唤醒该对象上等待的一个线程,从wait()方法返回。如果有多个线程在该对象上等待,唤醒哪一个由操作系统来决定的。
- notifyAll():唤醒该对象上等待的所有线程。
需要特别注意的是:能够调用这些方法的对象必须是锁对象,否则会抛出异常,所以,通常在synchronized中使用这些方法。其次,调用wait方法后,该线程会释放锁,调用notify和notifyAll后不会释放锁,继续向下执行任务。
public class SyncDemo {
private static Object o1 = new Object();
private static Object o2 = new Object();
public static void main(String[] args) {
Runnable r = () -> method1();
Thread thread = new Thread(r);
thread.start();
}
private static void method1() {
//o1是锁对象
synchronized (o1) {
try {
System.out.println("开始WAITING");
// o2并不是锁对象
o2.wait();
} catch (InterruptedException e) {
// e.printStackTrace();
}
}
}
结果如下,抛出了异常。
开始WAITING
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.mooc.Thread.SyncDemo.method1(SyncDemo.java:16)
at com.mooc.Thread.SyncDemo.lambda$main$0(SyncDemo.java:8)
at java.lang.Thread.run(Thread.java:748)
关于锁的释放时机:
public class SyncDemo {
private static Object o1 = new Object();
private static Object o2 = new Object();
public static void main(String[] args) {
Runnable r1 = SyncDemo::method1;
Runnable r2 = SyncDemo::method2;
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r2);
thread1.start();
thread2.start();
}
private static void method1() {
synchronized (o1) {
try {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到了锁");
System.out.println("线程:" + Thread.currentThread().getName() + " 等待");
o1.wait();
} catch (InterruptedException e) {
// e.printStackTrace();
}
}
}
private static void method2() {
synchronized (o1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到了锁");
for (; ; ) {
try {
Thread.sleep(1000);
System.out.println("线程:" + Thread.currentThread().getName() + " 在运行");
} catch (InterruptedException e) {
// e.printStackTrace();
}
}
}
}
}
结果如下,两个线程是同一个对象监视器,当Thread-0进入等待后,Thread-1仍然还可以执行,说明Thread-0已经释放了锁。
线程:Thread-0 获取到了锁
线程:Thread-0 等待
线程:Thread-1 获取到了锁
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
线程:Thread-1 在运行
- Condition 提供了几个方法:
- await()
- awaitUninterruptibly
- await(long time, TimeUnit unit)
- signal()
- signalAll() 方法的 作用见名知意,这里不做过多陈述。
- LockSupport
LockSupport是一个线程工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,也可以在任意位置唤醒。
- 核心方法: 1. park():阻塞线程 2. uppark(thread):唤醒一个指定的线程
- 与wait/notify对比:
- wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象,但是park不需要获取某个对象的锁就可以锁住线程。
- notify只能随机选择一个线程唤醒,无法唤醒指定的线程,unpark却可以唤醒一个指定的线程。
- 因为中断的时候
park
不会抛出InterruptedException
异常,所以需要在park
之后自行判断中断状态,然后做额外的处理。 - park()与uppark(thread)执行的顺序不重要,先uppark(thread)然后再park()也没关系。如下示例:
public class LockSupportDemo {
public static void main(String[] args) {
Thread thread = new Thread(new LockSupportDemoThread());
thread.start();
// 唤醒线程
LockSupport.unpark(thread);
System.out.println("唤醒完毕");
}
private static class LockSupportDemoThread implements Runnable{
@Override
public void run() {
try {
System.out.println("线程:"+ Thread.currentThread().getName()+" 开始执行");
// 在此处阻塞5s,保证此时unpark()方法在park()方法之前已经执行了
Thread.sleep(5000);
// 让线程进入WAITING
System.out.println("线程:"+ Thread.currentThread().getName()+" 即将进入等待");
LockSupport.park();
} catch (InterruptedException e) {
// e.printStackTrace();
}
System.out.println("线程:"+ Thread.currentThread().getName()+" 继续执行");
}
}
}
结果如下所示,是先执行的unpark,然后才park的,但是最终线程还是被唤醒了,说明顺序并不重要。
唤醒完毕
线程:Thread-0 开始执行
线程:Thread-0 即将进入等待
线程:Thread-0 继续执行
线程中断
中断是一种协同机制,可以是线程自己中断,也可以被其他线程中断。线程中断并不意味着线程结束了。
- 中断标识:每个线程都维护一个线程标志位,用以表示线程是否中断。
- 关于中断的几个重要方法:interrupt()、Thread.interrupted()、isInterrupted()
- interrupt()
- 如果线程是阻塞在wait、join、sleep方法中,则会抛出InterruptedException,并重置中断标志位
- 如果线程是阻塞在I/O操作InterruptibleChannel中,通道会关闭,标志位被设置(不是重置)。
- 如果线程是阻塞NIO的Selector中,会立即返回,标志位被设置(不是重置)。
- 如果线程不是处于以上3中情况中,则只是会设置标志位(不是重置)。
- Thread.interrupted()
public static boolean interrupted() {
return currentThread().isInterrupted(true);// true ,表示重置标志位
}
返回当前中断标志,并且重置标志位。
- isInterrupted()
返回当前中断标志,不会重置标志位。
public boolean isInterrupted() {
return isInterrupted(false);// false,不会重置标志位
}
Thread.interrupted()和isInterrupted()都是调用的native方法,只是入参不同。
private native boolean isInterrupted(boolean ClearInterrupted);
- 线程中断使用注意事项
- 查看当前的中断标识,是否被清空
- 关注InterruptedException
本文使用 tech.souyunku.com 排版