Java 线程基础

线程的那些事

线程模型


  • 基于用户线程
    相当于多线程共用一个内核线程,完全应用负责调度
    早期java实现,优点是不消耗内核资源,高并发,缺点是实现复杂

  • 基于内核线程
    内核有多个内核线程(Kernel-Level Thread,KLT)
    内核线程和轻量级进程(Light Weight Process,LWP)一一对应
    这样内核调度器调度内核线程效果上就调度了轻量级进程
    缺点是数量受到内核限制,并且系统调用用户态内核态切换代价高

调度模型


  • 抢占式调度(Preemptive Threads-Scheduling)
    系统控制时间片,线程抢占,时间片长度固定到时间系统就替换,没有阻塞问题
  • 协调式调度(Cooperative Threads-Scheduling)
    线程自身声明执行时间,执行完通知系统切换,优点是没有同步问题比如Lua,缺点是不可控可能长期占用阻塞

线程状态


Thread类内定义了State枚举,一共6种状态

Thread.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum State {
//先创建尚未启动
NEW,
//运行中或者已就绪等待时间片调度
RUNNABLE,
//阻塞,等待进入同步块
BLOCKED,
//等待
WAITING,
//限时等待
TIMED_WAITING,
//终结
TERMINATED;
}

状态转换

  • 阻塞:等待进入synchronized
  • 等待:wait,join,LockSupport#park及各类显式锁
  • 限时等待:sleep,wait,join,LockSupport#parkNanos,LockSupport#partUntil
  • 唤醒取锁:wait被notify,interrupt等机制唤醒后重新获取锁
  • 等待结束:等待自然结束,LockSupport#unpark

与操作系统状态区别

操作系统状态有初始,可运行(ready),运行(running),阻塞,终止

  • 线程没有单独的运行状态,本身时间片时间很短,没必要
  • 阻塞式IO, 线程状态是Runnable
    比如String input = new Scanner(System.in).nextLine();或者Socket socket = serverSocket.accept();
    在操作系统层面是休眠的,但是在java线程层面不是

线程操作


静态方法,作用于当前线程实例

Thread.java
1
2
3
4
// 线程进入等待状态一段时间,不释放锁
public static native void sleep(long millis) throws InterruptedException;
// 线程让出CPU避免过度占用,不释放锁。让出后还是运行状态,重新竞争CPU时间片,可能会被再次选中。效果和sleep(0)基本相同。如果底层操作系统不支持,就自旋到时间片结束,实现相似效果
public static native void yield();

实例方法,针对其他线程实例操作

Thread.java
1
2
3
4
5
6
7
8
9
// 当前线程等待,直到目标线程结束,基于wait-notify,当前线程放锁
public final void join() throws InterruptedException {
join(0);
}
// 向目标线程设置中断标记
public void interrupt() {
//...
interrupt0();
}