写在最前
之前写了ConcurrentHashMap,无论是1.8还是1.7,其中都会涉及锁Synchronized和ReentrantLock;锁的概念是为了解决线程安全问题;所以还是从线程开始写起。
这篇文章主要是写写Java线程的实现和线程的状态,以及上下文的概念,也有一些不完善的地方,会持续查缺补漏。
Java线程的实现
Java线程模型是基于操作系统原生线程模型的实现,在JDK中,windows版本和Linux版本都是使用一对一的线程模型实现的。
从上图大概可以知道,一条java线程对应到了一条轻量级进程(LWP),一条轻量级线程又映射到一条内核线程(KLT)。我们平时所说的线程,往往是指轻量级进程。我们在应用程序中创建或者操作的java.lang.Thread实例最终会映射到系统的内核线程。如果在无限制的创建java.lang.Thread实例,会影响系统的正常运行或崩溃。
线程的调度方式分为两种,协同式线程调度和抢占式线程调度。
Java线程最终会映射为系统内核原生线程,所以Java线程调度最终取决于系操作系统,而目前主流的操作系统内核线程调度基本都是使用抢占式线程调度,Java线程也是使用抢占式线程调度方式进行线程调度的。
很多系统都提供了优先级的概念,由于平台特性的问题,Java中的线程优先级和不同平台中系统线程优先级并不匹配,所以Java线程优先级可以仅仅理解为“建议优先级”。简单的说,java.lang.Thread#setPriority(int newPriority)并不一定生效,有可能Java线程的优先级会被系统自行改变。
Java线程的状态
从 JDK 源码找到Thread类的内部枚举类,可知线程一共有6种状态,分别是:新建,可运行,阻塞,无限期等待,有限期等待,终止。
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
下面这两张图,很好的描述了线程的状态和状态切换。
线程的状态:
线程的切换
NEW
线程创建出来,还未启动时为NEW状态。
Thread thread = new Thread();
System.out.println(thread.getState()); //NEW
RUNNABLE
线程调用了start方法后,就进入了RUNNABLE状态。RUNNABLE中有两个子状态,分别是READY、RUNNING。
- ready:该状态线程可以被线程调度器调度,成为RUNNING状态
- RUNNING:该状态线程表示此线程正在运行,线程对象中run方法中的代码指令被CPU调度。
当执行Thread.yeild()方法时或由于线程调度器调度,线程状态可能由RUNNING变为READY。但使用thread.getState()返回的状态仍然是RUNNABLE。
Thread thread = new Thread(()->{
while(true){
Thread.yield();
}
});
thread.start();
TimeUnit.SECONDS.sleep(2);
System.out.println(thread.getState()); //RUNNABLE
BLOCKING
在源码中对此状态解释的很好,直接放上源码的文档注释
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
此状态表示一个线程正在阻塞等待获取一个监视器锁。
一个阻塞状态线程等待进入同步代码块或者同步方法的监视器锁或者在调用了Object#wait()之后重入同步代码块或者同步方法。
上面两种情况,需要解释的更通俗一些:
-
一个阻塞状态线程等待进入同步代码块或者同步方法的监视器锁
这个比较好理解,线程在等待一个监听器锁,只有在获取监听器锁之后才能进入synchronized代码块或synchronized方法,在等待获取锁的过程中,都处于阻塞状态。
private static Object lock = new Object(); Thread thread1 = new Thread(()-> { synchronized (lock){ try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) {} } }); Thread thread2 = new Thread(()-> { synchronized (lock){ } } ); thread1.start(); Thread.sleep(50); thread2.start(); Thread.sleep(50); System.out.println(thread2.getState()); //BLOCKED
-
在调用了Object#wait()之后重入同步代码块或者同步方法。
线程在执行synchronized代码块或者synchronized方法中的代码时,调用了Object#wait方法,这时需要其他线程调用Object#notify()/notifyAll()进行显示唤醒,此时其他线程虽然唤醒了该线程,但是其他线程并未结束他的synchronized代码块或synchronized方法并释放锁,所以该线程然后处于阻塞状态。
import java.util.concurrent.TimeUnit; public class WaitNotify { private static Object lock = new Object(); private static boolean flag = true; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new Wait(),"Wait"); thread1.start(); TimeUnit.SECONDS.sleep(2); Thread thread2 = new Thread(new Notify(),"Notify"); thread2.start(); } static class Wait implements Runnable{ @Override public void run() { synchronized (lock){ while (flag){ try { lock.wait(); System.out.println(Thread.currentThread().getName() + "flag is true"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "flag is false"); } } } static class Notify implements Runnable{ @Override public void run() { synchronized (lock){ flag = false; lock.notifyAll(); try { TimeUnit.SECONDS.sleep(10); //此时wait线程处于BLOCKED状态 } catch (InterruptedException e) { e.printStackTrace(); } } } } }
WAITING
一个线程进入无限期等待状态的原因是调用了以下几个方法中的一个:
- 不带超时的Object#wait()
- 不带超时的Thread#join()
- LockSupport.park()
调用以上方法的线程会等待另外的线程做出处理,分别如下:
- 调用了Object#wait()的线程等待另外线程调用Object#notify()或者Object#notifyAll()唤醒
- 调用了Thread#join()的线程等待另一个线程终结
- 调用了LockSupport.park()等待LockSupport.unpart(thread)唤醒
其中Thread#join()方法比较特殊,它会阻塞线程实例直到线程实例执行完毕。下面是JDK 1.8源码:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) { //当前线程存活时,会一直阻塞
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
WAITING状态下不会分配CPU执行时间,直到被显示唤醒,线程状态由WAITING变为RUNNABLE然后继续执行。
Thread thread = new Thread(()-> {
LockSupport.park();
while (true){
Thread.yield();
}
}
);
thread.start();
Thread.sleep(50);
System.out.println(thread.getState()); //WAITING
TIMED WAITING
一个线程进入有限期等待状态的原因是调用了以下几个方法中的一个:
- Thread.sleep(long millis)
- 带超时的Object#wait(long timeout)
- 带超时的Thread#join(long millis)
- LockSupport.parkNanos(Object blocker, long nanos)
- LockSupport.parkUntil(Object blocker, long nanos)
TIMED WAITING状态下不会分配CPU执行时间,不过这种状态下不需要显示唤醒,到达超时期限会被VM唤醒。
Thread thread = new Thread(()-> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {}
}
);
thread.start();
Thread.sleep(50);
System.out.println(thread.getState()); //TIMED WAITING
TERMINATED
该线程执行完毕,就会进入TERMINATED状态。
一个线程只能被启动一次,调用一次Thread#run()方法,进入此状态,证明此线程生命周期结束。
Thread thread = new Thread(() -> {
}
);
thread.start();
Thread.sleep(50);
System.out.println(thread.getState()); //TERMINATED
上下文切换
多线程环境中,线程状态从RUNNABLE切换为非RUNNABLE时,需要保存相应的上下文信息。反之,从非RUNNABLE切换为RUNNABLE时,就需要读取上下文信息,在之前的执行进度上继续执行。
对线程上下文信息的保存与恢复称为上下文切换。
参考
-
JDK 1.8源码