Java线程简介

本文将介绍Java线程的状态、线程的中断、线程间通信和线程的实现。

线程的状态

Java语言定义了6种不同的线程状态,在给定的一个时刻,线程只能处于其中的一个状态。


线程状态

线程状态的转换关系,如下图所示:


线程的中断

当希望终止一个线程时,并不是简单的调用 stop 命令。虽然 api 仍然可以调用,但是和其他的线程控制方法如 suspend、resume 一样都是过期了的不建议使用的方法。就拿 stop 来说,stop 方法在结束一个线程时并不会保证线程的资源正常释放,因此会导致程序可能出现一些不确定的状态。要优雅的终止一个线程,可以使用中断的方式。

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。

线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。

另外许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。

public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态

通过下面这个例子,可实现线程终止的逻辑

//通过中断的方式
public class InterruptDemo {
private static int i;
public static void main(String[] args) throws
InterruptedException {
Thread thread=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Num:"+i);
},"interruptDemo");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}}
//通过标记符的方式
public class VolatileDemo {
private volatile static boolean stop=false;
public static void main(String[] args) throws
InterruptedException {
Thread thread=new Thread(()->{
int i=0;
while(!stop){
i++;
}
});
thread.start();
System.out.println("begin start thread");
Thread.sleep(1000);
stop=true;
}
}

这种通过标识符或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。

线程的通信

为了支持多线程之间的协作,JDK提供了两个非常重要的方法wait()notify()。这两个方法并不是在Thread类中,而是Object类。这也意味着任何对象都可以调用这两个方法。
这两个方法的签名如下,都是本地方法:

 public final native void wait(long timeout) throws InterruptedException;
 public final native void notify();

调用wait()方法,首先会获取监视器锁,获得成功以后,会让当前线程进入等待状态进入等待队列并且释放锁;然后当其他线程调用notify()或者notifyAll()以后,会选择从等待队列中唤醒任意一个线程,而执行完notify()方法以后,并不会立马唤醒线程,原因是当前的线程仍然持有这把锁,处于等待状态的线程无法获得锁。必须要等到当前的线程执行完按monitorexit指令以后,也就是锁被释放以后,处于等待队列中的线程就可以开始竞争锁。竞争是不公平的,并不是先等待的线程就会被优先选择。


public class ThreadWait extends Thread{
  private Object lock;
  public ThreadWait(Object lock) {
    this.lock = lock;
 }
  @Override
  public void run() {
    synchronized (lock){
      System.out.println("开始执行 thread wait");
      try {
        lock.wait();
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
      System.out.println("执行结束 thread wait");
   }
 }
}

public class ThreadNotify extends Thread{
  private Object lock;
public ThreadNotify(Object lock) {
    this.lock = lock;
 }
  @Override
  public void run() {
    synchronized (lock){
      System.out.println("开始执行 thread notify");
      lock.notify();
      System.out.println("执行结束 thread notify");
   }
 }
}

注:因为在执行wait()和notify()时都必须获取到锁,所以wait和notify都需要在synchronized里面。wait()与sleep()的区别,sleep()不会释放释放锁。

join():在很多情况下,一个线程的输入可能非常依赖于另外一个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。JDK提供了join()操作来实现这个功能。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。例如下面的demo,创建了10个线程,编号0~9,每个线程调用前一个线程的join()方法。

public class Join {
public static void main(String[] args) throws Exception {
Thread previous = Thread.currentThread();
for (int i = 0; i < 10; i++) {
// 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
Thread thread = new Thread(new Domino(previous), String.valueOf(i));
thread.start();
previous = thread;
}
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + " terminate.");
}
static class Domino implements Runnable {
private Thread thread;
public Domino(Thread thread) {
this.thread = thread;
}
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
}

//输出如下:
main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.

yield():表示当前线程让出CPU,当前线程让出CPU后,还会进行CPU资源的争夺。

线程的实现

我们注意到Thread类和大部分的Java API有显著的差别,它的所有关键方法都是声明为Native的。一个Native方法往往意味着这个方法没有使用或无法使用平台无关的手段来实现(当然也可能是为了执行效率而使用Native方法,不过,通常最高效率的手段也就是平台相关的手段)。
实现线程主要有3种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。
使用内核线程(Kernel-Level Thread,KLT)实现:就是直接由操作系统内核支持的线程,由内核完成线程切换,内核通过操纵调度器进行线程调度,并且负责将线程的任务映射到各个处理器上。程序一般不会直接使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process ,LWP),轻量级进程就是我们通常意义上讲的线程。轻量级进程和内核线程之间1:1的关系称为一对一线程模型。

局限性:
基于内核实现,各种线程操作需要进行系统调用,而系统调用的代价相对较高,需要在用户态(User Model)和内核态(Kernel Model)中来回切换。
每个轻量级进程都需要一个内核线程支持,轻量级进程要消耗一定的内核资源,因此一个系统支持轻量级进程的数量是有限的。
使用用户线程实现:狭义的用户线程指的是完全建立在用户空间的线程库,系统内核不能感知线程存在。进程与线程之间的1:N的关系称为一对多的线程模型。优势在于不需要内核支援,劣势也在于没有内核支援,所以的线程操作都需要用户程序自己处理。目前使用用户线程的程序越来越少。


使用用户线程加轻量级进程混合实现:用户线程完全建立在用户空间,用户线程的创建、切换、析构的操作比较廉价,并且支持大规模的用户线程并发。操作系统提供支持的轻量级进程则作为用户线程和内核线程的桥梁使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。用户线程与轻量级进程的数量比是不定的,即为N:M的关系。

线程的调度

线程调度是指系统为线程分配处理器使用权的过程,主要包括两种方式。
协同式线程调度:执行时间由线程本身决定,执行完后,要主动通知系统切换到另一个线程。好处是实现简单;切换操作对线程自己可知,所以没有什么同步问题;坏处是执行时间不可控,可能会出现程序一直阻塞的情况。
抢占式线程调度:线程由系统分配执行时间,切换不由线程本身决定。执行时间系统可控,不会有一个线程导致整个进程阻塞的情况。Java使用的线程调度方式是抢占式线程调度

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 前言:虽然自己平时都在用多线程,也能完成基本的工作需求,但总觉得,还是对线程没有一个系统的概念,所以,查阅了一些资...
    justCode_阅读 699评论 0 9
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,084评论 0 23
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,630评论 0 6
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 11,232评论 4 56
  • 你们在回忆的彼岸,浅笑盈盈。那道时光的河,便是亘古的天堑,我只能凝神顾盼,却如何,也感觉不到你们的存在。是太远了吧...
    风籁铉月阅读 354评论 3 2