Java中Thread详解(一篇就够了)

前言

操作系统中,一个进程往往代表着一个应用程序实例,而线程是进程中轻量级的调度单元,也可以看作是轻量级的进程,可以共享进程资源。下面简单介绍在操作系统中线程通用实现方式。接下来内容主要对线程模型进行简单介绍,然后对Java线程实现Thread类进行了解。

线程模型

暂且抛开Java线程,先说明一下在操作系统中,线程通用的几种实现方式。实现线程主要有三种方式。

内核线程模型

使用内核线程实现的方式,通常也被成为1 : 1实现模型。内核线程(Kernel Level Thread,KLT)是直接由操作系统内核来支持的线程,这种线程由内核来控制切换,内核通过调度器(Scheduler)来对线程进行调度,并负责将线程任务映射到各个处理器,在多核操作系统中具有能力并行处理多个任务,这种支持多线程的内核被称为多线程内核(Mutil-Threads Kernel)。
程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口(轻量级进程Light Weight Process)LWP,也就是通常意义所描述的线程,每个轻量级进程都由一个内核支持,因此先支持内核线程,才能有轻量级进程。其中KLT、Schedule、LWP之间关系如下图所示:

Java中Thread详解(一篇就够了)

 

在这个模型中,每个轻量级进程都是一个调度单元,单个KLT的阻塞不会影响其他单元的调度,当然也有其本身的局限性,由于是基于内核线程进行创建的,所以线程的各种操作,如创建、同步等都需要进行系统调用。应用程序运行在用户空间,内核处于用户态(User Mode),系统调用需要进行用户态和内核态(Kernel Mode)切换,频繁的进行用户态内核态切换,会严重消耗系统性能。此外,每个KLT都需要内核来支持,因此也会消耗内核资源如内核线程空间等,因此这种模型下所能创建的线程数也是很有限的。

用户线程模型

使用用户线程实现的方式被称为1 : N模型,非内核线程都可以被看作是用户线程(User Thread,UT)的一种。这里需要辩证的看待关于用户线程的定义,广义上来从非内核线程的角度看,轻量级进程也属于用户线程的范畴,但是由于其建立在内核基础之上,过于依赖系统调用,又不具备通常意义上用户线程的优点。其实现模型如图所示:

Java中Thread详解(一篇就够了)

 

狭义上所说的用户线程指完全建立在用户空间的线程,线程的控制无需内核参与,内核也无法感知其实现模式,这种线程也不需要进行用户态和内核态的切换,因此用户线程对资源的使用率较小,支持大规模的线程数量。部分高性能数据库中的线程就是完全由这种类型的用户线程来实现。
用户线程的优势也是其劣势,由于没有内核的支持,所有线程都需要由用户程序处理。操作系统通常只会进行进程级别资源的分配,那么在用户空间,用户线程如何创建、销毁、切换、阻塞以及调度都要由用户来处理,因此用户线程实现的程序也提高了复杂度和故障机率,除非是大规模的线程需要场景下,否则一般不倾向于使用用户线程。
Java中的线程曾经使用用户线程实现,最终由于其复杂度被放弃。近年来,以高并发为特性的语言,如Golang、Erlang中使用用户线程的场景有所提升。

混合模型

基于使用内核线程和用户线程的模型,还有一种就是整合两种模型特性的混合实现模型,也被成为M : N模型。这种模型中即使用了内核线程,也使用了用户线程,通过轻量级进程作为用户线程和内核线程之间的桥梁,既可以使用内核提供的线程进行调度及处理器映射,同时也兼具了大规模用户线程并发。这种模型下用户线程和轻量级进程的数量比不固定,如下图所示:

Java中Thread详解(一篇就够了)

 

在许多UNIX类操作系统,如Solaris、HP-UX等都提供了这种M : N的线程模型实现。

Java线程实现

实现模型

Java虚拟机规范中并没有定义关于线程如何实现,因此不同虚拟机厂商实现方式也没有统一标准。在JDK1.2前的线程,早期的Classic虚拟机中,基于一种被称为绿色线程的用户线程来实现。但在JDK1.3之后,商用Java虚拟机普遍开始使用内核线程模型,即1 : 1模型来作为Java线程的实现。
在HotSpot虚拟机中,每个Java线程都是直接映射到操作系统的原生线程来实现,虚拟机本身并不会干预线程的创建、调度,但是可以设置线程的优先级给予操作系统建议。关于线程的冻结或者唤醒、线程由那颗CPU核心执行以及可使用的CPU执行时间片都由操作系统来完成。
当然也有特殊场景,用于JavaME的CLDC HotSpot Implementation同时支持1 : N以及特殊的混合模型。Solaris平台的HotSpot,由于操作系统的特性,因此同时支持1 : 1和N : M的两种线程模型,通过虚拟机参数设置。
总体来说,Java线程模型的实现通常依赖于所运行的操作系统内核提供的支持,在不同平台并不能达成一致,因此Java虚拟机规范也未明确的做出定义。另外,至于使用何种线程模型,其所影响的只有线程规模(线程数量)和操作成本。

调度策略

线程无论基于何种模型创建,都有其调度策略,线程的调度指的是操作系统为线程分配使用权的过程。通常调度方式包含两种,分别是协同式(Cooperative Threads Scheduling)和抢占式(Preemptive Threads Scheduling):

  1. 协同式调度

使用协同式调度方式的线程调度由其本身来控制,线程在自身工作执行完成后,主动通知系统切换到另一个线程执行,这种方式实现简单,便于控制。但是过于依赖线程本身来控制调度,如果某个线程执行任务的程序存在问题就会导致一直阻塞。

     2. 抢占式调度

使用抢占式调度方式的多线程系统,线程的调度由系统分配执行时间,线程的切换由系统决定。这种调度方式下,线程的执行时间可控,不会因单个线程问题导致应用程序堵塞。Java中所使用的线程调度策略就是抢占式,虽然整个调度基于系统来确定,但是可以通过设置优先级的方式给予操作系统一定的建议,总共包含1~10优先级,优先级越高,线程越容易被选择执行。

Thread类

Java语言是支持多线程的,一个正在运行的Java程序可以称之为一个进程(process),在每个进程里面包含多个线程,线程是进程中单一的顺序控制流,CPU在执行计算机指令的时候都是按顺序执行,但是由于其执行速度很快,可以把时间分成很细小的时间片,交替执行,线程和进程的区别在于:

  • 创建进程的开销大于创建线程的开销,进程之间的通信比线程间要难
  • 线程不能独立存在,依托于进程而存在,线程也可以看作轻量级的进程
  • 多进程的稳定性高于多线程,一个进程的运行不会影响其他进程,但线程崩溃往往会引起程序的崩溃

Thread类位于java.lang包,JDK1.0引入。在HotSpot虚拟机中,线程使用的是基于操作系统的1 : 1的内核实现模型来创建线程,线程的创建、调度、执行、销毁等由内核进行控制,调度过程通过抢占式策略进行调度。

生命周期

Java语言定了线程生命周期的6种状态,在任意时刻线程只能处于一种状态,Java中提供了特定的API,在某些场景下可以实现线程间状态的转换。这6种状态使用枚举类java.lang.Thread.State来表示,每种状态如下所示:

  • NEW(新建状态):新创建后尚未启动的线程处于这种状态
  • RUNNABLE(运行状态):Java虚拟机中处于该状态的线程,对应操作系统状态中的Running和Ready,表示该线程正在执行或者等待操作系统分配执行时间
  • BLOCKED(阻塞状态):当多个线程进行资源争抢,等待获取一个排他锁的线程会处于该状态,当线程获取到锁之后状态会变为RUNNABLE状态,其他等待锁的线程继续处于BLOCKED状态,一般被synchronized关键字修饰或者ReentrantLock包裹的代码快会触发不可到到达线程处于该状态。
  • WAITING(无限期等待状态):处于该状态的线程不会被分配执行时间,需要等待被其他线程显式唤醒,调用Thread中以下方法会触发线程处于该状态:
    • Object.wait() with no timeout
    • Thread.join() with no timeout
    • LockSupport.park()
  • TIMED_WAITING(限期等待状态):计时等待状态的线程,不会被操作系统分配执行时间,也不需要被其他线程显式唤醒,在计时结束后由操作系统自动唤醒,Thread中的以下方法会触发线程进入此状态:
    • Thread.sleep()
    • Object.wait() with timeout
    • Thread.join() with timeout
    • LockSupport.parkNanos()
    • LockSupport.parkUntil()
  • TERMINATED(终止状态):线程任务执行结束时处于这种状态

线程的生命周期以及状态转换如下图所示:

Java中Thread详解(一篇就够了)

 

接下来对Thread对象的声明周期包含的六种状态进行进行简单模拟。

NEW

public class Test{
    public static void main(String[] args) throws Exception{
        System.out.println("Thread State is:"+new Thread().getState());
    }
}

输出结果

Thread State is:NEW

RUNNABLE

public class Test implements Runnable{
    @Override
    public void run() {
        System.out.println("Thread State is:"+Thread.currentThread().getState());
    }
    public static void main(String[] args) throws Exception{
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
    }
}

输出结果

Thread State is:RUNNABLE

BLOCKED

  • 创建线程T1,T2调用start()方法,T1,T2状态均为RUNNABLE
  • 若T1获得锁,T1状态为RUNNABLE,T2状态变为BLOCKED
  • 等待T1执行完释放锁,T2获得锁,T2状态RUNNABLE
class BlockThread extends Thread {
    private String name;    //当前线程名称
    private Object lock;    //锁
    public BlockThread(Object lock,String name){
        this.lock = lock;
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println("Thread "+name+" State is "+Thread.currentThread().getState());
        synchronized (lock) {
            System.out.println("Thread "+name+" hold the lock");
            try {
                System.out.println("Thread "+name+" State is "+Thread.currentThread().getState());
                Thread.sleep(1000 * 10);    //抢到锁的线程执行逻辑,这里用睡眠模拟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread " + name + " release the lock");
        }
    }
}
public class Test{
    public static void main(String[] args) throws Exception{
        Object lock = new Object();//锁
        BlockThread t1 = new BlockThread(lock,"T1");
        BlockThread t2 = new BlockThread(lock,"T2");
        t1.start(); //线程 T1开始运行
        t2.start(); //线程 T2开始运行
        Thread.sleep(100);  //阻塞主线程,等待T1,T2抢锁
        System.out.println("Thread T1 State is " + t1.getState());  //获取T1线程状态
        System.out.println("Thread T2 State is " + t2.getState());  //获取T2线程状态
    }
}

输出结果

Thread T1 State is RUNNABLE
Thread T1 hold the lock
Thread T1 State is RUNNABLE
Thread T2 State is RUNNABLE
Thread T1 State is TIMED_WAITING
Thread T2 State is BLOCKED
Thread T1 release the lock
Thread T2 hold the lock
Thread T2 State is RUNNABLE
Thread T2 release the lock

WAITING

class WaitingThread extends Thread {
    private Object lock;
    public WaitingThread(String name, Object lock) {
        super(name);
        this.lock = lock;
    }
    
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName()+" try to wait");
        synchronized (lock) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test{
    public static void main(String[] args) throws Exception {
        Object lock = new Object();
        WaitingThread t = new WaitingThread("T", lock);
        t.start();
        Thread.sleep(1000);
        System.out.println("Thread T State is " + t.getState());
        System.out.println("Thread "+Thread.currentThread().getName()+" State is " + Thread.currentThread().getState());
    }
}

输出结果

Thread T try to wait
Thread T State is WAITING
Thread main State is RUNNABLE

TIMED_WAITING

线程调用sleep()方法,进入TIMED_WAITING状态

class WaitingThread extends Thread {
    private Object lock;
    public WaitingThread(String name, Object lock) {
        super(name);
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test{
    public static void main(String[] args) throws Exception {
        Object lock = new Object();
        WaitingThread t1 = new WaitingThread("T1", lock);
        WaitingThread t2 = new WaitingThread("T2", lock);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("Thread T1 State is " + t1.getState());
        System.out.println("Thread T2 State is " + t2.getState());
    }
}

输出结果

Thread T1 State is TERMINATED
Thread T2 State is TIMED_WAITING

TERMINATED

public class Test implements Runnable{
    @Override
    public void run() {

    }
    public static void main(String[] args) throws Exception{
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println("Thread State is "+thread.getState());
    }
}

输出结果

Thread State is TERMINATED

构造方法

Java中的线程相关操作由Thread类实现,Thread类位于java.lang包,于JDK 1.0版本引入。Thread类封装了多线程操作的上层API,可执行单元代码逻辑通过实现Runnable接口,重写run方法完成,Thread类本身也实现了该接口,接口定义如下所示:

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

在JDK1.8 引入函数式编程后,Runnable接口也被@FunctionalInterface注解标记为函数式接口,支持Lambda表达式。在JDK1.8中,Thread类提供了如下构造方法:

  • Thread() :创建一个默认设置的线程对象实例
  • Thread(Runnable target) :创建一个包含可执行对象的线程实例
  • Thread(Runnable target, String name) :创建一个包含可执行对象,指定名称的线程对象
  • Thread(String name):创建一个指定名称的线程对象
  • Thread(ThreadGroup group, Runnable target) :创建一个指定线程组,包含可执行对象的线程对象实例
  • Thread(ThreadGroup group, Runnable target, String name) :创建一个指定线程组,包含可执行对象,指定线程名称的线程对象实例
  • Thread(ThreadGroup group, Runnable target, String name, long stackSize) :创建一个指定线程组、包含可执行对象、指定名称以及堆栈大小的线程对象实例
  • Thread(ThreadGroup group, String name):创建一个指定线程组,线程名称的线程实例

说明:关于线程组(ThreadGroup类),一个线程组代表一组线程。此外,一个线程组还可以包括其他线程组。线程组形成一棵树,其中除了初始线程组之外的每个线程组都有一个父级。允许线程访问有关其自己的线程组的信息,但不能访问有关其线程组的父线程组或任何其他线程组的信息。

优先级

Java中创建的线程,每个线程都有一个优先级,具有较高优先级的线程优先于具有较低优先级的线程执行。当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级。Thread类中定义了以下三个默认优先级:

// 线程所能设置的最小优先级
public final static int MIN_PRIORITY = 1;

// 创建线程的默认优先级
public final static int NORM_PRIORITY = 5;

// 线程所能设置的最大优先级
public final static int MAX_PRIORITY = 10;

创建线程

Java中如何创建一个线程Thread,可以继承Thread类、实现Runnable或者Callable接口进行显式的创建线程。需要注意的是Runnable接口只是提供了自定义任务执行单元的方法逻辑,而真正关于线程的上层API操作实现还是位于Thread类。查看Thread中重写的run方法源码:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

虽然Thread类实现了run方法,但是并没有做任何的特殊处理,真正的任务执行单元,会交给构造方法传入的可执行对象完成逻辑处理,这里使用的是模板方法设计模式。

关于Callable接口下面会详细介绍。

继承Thread类

通过继承Thread类,并重写run方法。代码如下:

public class Test extends Thread{
    @Override
    public void run() {
        System.out.println("Thread is Created");
    }
    public static void main(String[] args) {
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println(Thread.currentThread().getName());
    }
}

在主线程创建新的线程,二者的执行时机由系统调度确定,因此输出结果是随机的,如下所示:

/*新建线程先执行*/
Thread is Created
main
/*主线程先执行*/
main
Thread is Created

实现Runnable接口

通过实现Runnable接口并且重写run方法来创建一个线程。代码如下

public class Test implements Runnable{
    @Override
    public void run() {
        System.out.println("Thread is Created");
    }
    public static void main(String[] args) {
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println(Thread.currentThread().getName());
    }
}

输出结果

/*新建线程先执行*/
Thread is Created
main
/*主线程先执行*/
main
Thread is Created

实现Callable接口

严格来讲,参考Oracle官方文档,创建一个Thread只有这两种方式,无论实现Runable接口,还是继承Thread类,都存在一些缺陷,我们无法获得线程的执行结果,无法处理执行过程的异常,这里提供另外一种创建线程的方式。
Callable是JDK 1.5新增的接口,位于java.util.concurrent 包下,Callable接口里面定义了call方法,call方法是run方法的增强版,可以通过实现Callable接口时传入泛型来指定call方法的返回值,并且可以声明抛出异常。

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Thread中无论哪一种构造方法都没有Callable类型的target,只能传入Runnable类型的target,那如何把Thread和Callable联系起来,这里就要引入Future接口和FutureTask实现类。

Future接口定义如下方法

/**尝试取消执行此任务。*/
boolean cancel(boolean mayInterruptIfRunning) 
/**等待计算完成,然后检索其结果。*/  
V get() 
/**如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。*/  
V get(long timeout, TimeUnit unit) 
/**如果此任务在正常完成之前被取消,则返回 true 。*/  
boolean isCancelled() 
/**返回 true如果任务已完成。*/  
boolean isDone()

查看源码,RunnableFuture作为一个过渡同时继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

查看FutureTask的类图关系image

FutureTask类提供了两个构造方法

  • FutureTask(Callable callable):创建一个包含Callable可执行对象的FutureTask实例。
  • FutureTask(Runnable runnable, V result):创建一个包含Runnable可执行对象及执行结果的FutureTask实例,参数由RunnableAdapter进行适配包装。

再回到最初的问题如何将实现了Callable接口的线程类作为Thread实例的target,这里经过了以下过程

  • 创建线程类实现Callable接口,重写call方法
  • 创建FutureTask实例,将实现Callable接口的线程类实例化对象作为FutureTask的target
  • 创建Thread类,将FutureTask实例化对象作为Thread的target

Future接口和FutureTask类在中间做了一层包装,代码展示如下

public class Test implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("Thread is Created");
        return "OK";
    }
    public static void main(String[] args) throws Exception {
        Test test = new Test();
        FutureTask futureTask = new FutureTask(test);
        Thread thread = new Thread(futureTask);
        thread.start();
        String str = (String) futureTask.get(5,TimeUnit.SECONDS);
        System.out.println(str);
        System.out.println(Thread.currentThread().getName());
    }
}

输出结果

Thread is Created
OK
main

native方法

Java中线程采用内核线程模型来实现用户程序中的线程,因此一些常用方法依托于虚拟机原生实现,下面介绍这些native方法的基本使用。

yield

yield方法是一个native方法,由C++底层进行关于操作系统层面的逻辑处理。yield的字面意思是退让。调用该方法会向调度程序提示当前线程愿意放弃其当前对处理器的使用,调度程序可以随意忽略此提示。
yield是一种启发式尝试,使用它可以改善线程之间的相对进展,否则会过度使用 CPU。在使用yield方法时通常有下面两种使用场景:

  • yield的使用应与详细的分析和基准测试相结合,以确保实际上具有预期的效果,但很少使用这种方法。对于调试或测试目的可能很有用,它可能有助于重现由于竞争条件导致的错误
  • 在设计并发控制结构(例如 java.util.concurrent.locks 包中的结构)时,它也可能很有用

如下代码所示

public class TestYield {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");
        thread1.start();
        thread2.start();
    }

    private static class MyThread extends Thread {
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                if (i % 2 == 0) {
                    Thread.yield();
                    System.out.println(getName() + ":" + i);
                }
            }
        }
    }
}

join

join方法让一个线程加入到另一个线程之前执行,在此线程执行期间,其他线程进入阻塞状态,当然也可以指定join入参(指定执行等待的超时时间),最多等待几毫秒让该线程终止,超时0意味着永远等待。
此实现使用以this.isAlive为条件的this.wait调用循环,当线程终止时,将调用this.notifyAll方法。建议应用程序不要在Thread实例上使用wait、notify或notifyAll。如果任何线程中断了当前线程,会抛出InterruptedException异常时清除当前线程的中断状态。

public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");

        thread1.start();
        thread1.join();
        thread2.start();
    }
    private static class MyThread extends Thread {
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println(getName());
            }
        }
    }
}

sleep

当调用线程的sleep方法,使当前执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度程序的精度和准确性。如果任何线程中断了当前线程,会抛出InterruptedException异常时清除当前线程的中断状态。

interrupt

使用interrupt方法会中断这个线程,除非当前线程正在中断自己,否则会调用该线程的checkAccess方法,这可能会导致抛出SecurityException。主要有以下几种场景:

  • 如果一个线程被Object类的wait、或者Thread的join、sleep方法调用处于阻塞状态时,那么它的中断状态会被清除并且会收到一个InterruptedException。
  • 如果该线程在InterruptibleChannel上的IO操作中被阻塞,则通道将关闭,线程的中断状态将被设置,线程将抛出 java.nio.channels.ClosedByInterruptException。
  • 如果该线程在java.nio.channels.Selector中被阻塞,则该线程的中断状态将被设置,并且它将立即从选择操作返回,可能带有非零值,就像调用了选择器的唤醒方法一样。如果前面的条件都不成立,则将设置该线程的中断状态。
public class TestInterrupt {
    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread");

        thread.start();
        thread.interrupt();
    }
}

输出结果

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.starsray.test.api.TestInterrupt.lambda$main$0(TestInterrupt.java:8)
	at java.lang.Thread.run(Thread.java:748)

其他

这里就不一一演示,列举在Thread中提供的其他方法。

  • activeCount():返回当前线程的thread group及其子组中活动线程数的估计
  • checkAccess():确定当前正在运行的线程是否有权限修改此线程
  • clone():将CloneNotSupportedException作为线程抛出无法有意义地克隆
  • currentThread():返回对当前正在执行的线程对象的引用
  • dumpStack():将当前线程的堆栈跟踪打印到标准错误流
  • enumerate(Thread[] tarray):将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中
  • getAllStackTraces():返回所有活动线程的堆栈跟踪图
  • getContextClassLoader():返回此Thread的上下文ClassLoader
  • getDefaultUncaughtExceptionHandler():返回线程由于未捕获异常突然终止而调用的默认方法
  • getId():返回此线程的标识符
  • getName():返回此线程的名称
  • getPriority():返回此线程的优先级
  • getStackTrace():返回表示此线程的堆栈转储的堆栈跟踪元素数组。
  • getState():返回此线程的状态
  • getThreadGroup():返回此线程所属的线程组
  • getUncaughtExceptionHandler():返回由于未捕获的异常,此线程突然终止时调用的处理程序
  • holdsLock(Object obj):返回 true当且仅当当前线程在指定的对象上保持监视器锁
  • interrupt():中断这个线程
  • interrupted():返回当前线程是否中断
  • isAlive():返回这个线程是否活着
  • isDaemon():返回这个线程是否是守护线程
  • isInterrupted(boolean ClearInterrupted):测试某个线程是否已被中断
  • setContextClassLoader(ClassLoader cl):设置此线程的上下文ClassLoader。
  • setDaemon(boolean on):将此线程标记为 daemon线程或用户线程。
  • ...

守护线程

Java中线程除了用户工作线程外还有一种特殊的线程被称为守护线程。通过setDaemon(true)方法将此线程标记为守护线程或用户线程。当虚拟机中唯一运行的线程都是守护线程时,Java虚拟机退出。该方法必须在线程启动前调用。
守护线程最主要的作用就是服务于虚拟机中的非守护线程,比如虚拟机中的垃圾回收线程就是典型的守护线程。需要注意的是如果是在守护线程中产生的线程,那么这个线程依然是守护线程。如代码所示:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TestDaemon {
    private static volatile boolean restFlag = false;
    private static final int threshold = 5;

    public static void main(String[] args) throws InterruptedException, IOException {
        List<Integer> list = new ArrayList<>();
        // 工作线程
        Thread worker = new Thread(() -> {
            while (!restFlag) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (!restFlag) {
                    list.add(1);
                    System.out.println("worker is working");
                }
            }
            System.out.println("worker rest");
        }, "work-thread");

        // 守护线程
        Thread employers = new Thread(() -> {
            while (!restFlag) {
                if (list.size() >= threshold) {
                    restFlag = true;
                    System.out.println("employers exit");
                }
            }
        }, "employers-thread");

        employers.setDaemon(true);
        worker.start();
        employers.start();
    }
}

总结

Java中线程使用的是与操作系统1 : 1的内核线程模型,因此线程的创建、运行等依赖于操作系统的调度。线程生命周期中包含六种状态,线程调用不同的方法可以在RUNNING、WATING、TIMED_WAITING、BLOCK之间相互转换。
这里总结一下三种创建线程的方法特点

  • 继承Thread类实现多线程:
    • 实现起来简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程
    • 线程类已经继承Thread类了,就不能再继承其他类
    • 多个线程不能共享同一份资源
  • 通过实现Runnable接口或者Callable接口实现多线程:
    • 线程类只是实现了接口,还可以继承其他类
    • 多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况
    • 通过这种方式实现多线程,相较于第一类方式,编程较复杂
    • 要访问当前线程,必须调用Thread.currentThread()方法

一般推荐使用以实现接口的方式创建线程类。

版权声明:程序员胖胖胖虎阿 发表于 2022年11月22日 下午3:40。
转载请注明:Java中Thread详解(一篇就够了) | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...