写于:2019-11-09

Thread 有很多 API ,下面介绍几种常用的 API,更多信息参考Java Doc

# 一、setDaemon:设置守护线程

作用:将线程设置为守护线程

setDaemon java Doc 描述

1、该方法将线程设置为守护线程。

2、该方法的调用必须要在线程 start 之前。

小贴士

当 JVM 中没有非守护线程时,JVM 进程会退出。

# 1、什么是守护线程

一个简单的代码

public class SimpleThread {
    public static void main(String[] args) {
        // JVM 中只要存在一个非守护线程,JVM 就不会退出
        // === main 线程开始
        // 创建一个线程
        Thread thread = new Thread(() -> {
            while (true) {
                sleep(1_000);
            }
        });
        ////////////////////////////////////////
        thread.setDaemon(true); // 设置为守护线程
        ////////////////////////////////////////
        thread.start();
        // === main 线程结束
    }

    public static void sleep(long mill){
        try {
            Thread.sleep(mill);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上述代码中存在两个线程,一个 main 线程,一个 thread 线程。

a、thread 默认为非守护线程,即使 main 线程结束了,JVM 也不会退出

b、调用 setDaemon 方法,将 thread 设置为守护线程,此时 main 线程为 thread 父线程,也是主线程,thread 会随着 main 线程生命周期的结束,而结束。此时由于没有非守护线程存在,JVM 退出。

# 2、守护线程的作用

如果一个 JVM 没有一个非守护线程,此时 JVM 会退出,也就是说守护线程具备自动结束生命周期的特征。

守护线程适合用于执行一些后台任务,当关闭某些线程,或者退出 JVM 时,这些守护线程会跟着退出。

# 二、sleep:线程进入休眠

作用:让线程进入休眠

setDaemon java Doc 描述

上述的两种方法都是让线程进入休眠,只是精度不同。

小贴士

当线程进入休眠时,此时并不会放弃 monitor 锁的所有权。

# 三、yield:线程发出释放CPU执行权信号

作用:让调用的线程放弃 CPU 执行权。使得线程从 RUNNING 状态进入 RUNNABLE 状态。

小贴士

在 CPU 资源不紧张时,调用 yield 方法,CPU 可能不予以理会。

# 四、setPriority:设置优先级

作用:设置线程优先级。让线程更有机会获取到CPU执行权,但并一定会,该操作仅仅是对 CPU 发出了一个提示。

# 1、线程优先级概念

进程有进程的优先级,线程也有线程的优先级,理论上优先级比较高的线程更有机会获取到CPU的执行权,但是设置线程优先级,仅仅是给 CPU 发送了一个提示操作,具体如下:

  • 对于 root 用户,该方法会提示 操作系统你想要设置的优先级级别。
  • 如果 CPU 比较忙,设置优先级可能会获取到更多的 CPU 时间片,但是闲暇时优先级的高低几乎不起作用。

# 2、通过源码进一步了解优先级设置相关信息

代码如下

public class Thread implements Runnable {
	/** The minimum priority that a thread can have. **/
    public final static int MIN_PRIORITY = 1;
	 /** The maximum priority that a thread can have. **/
    public final static int MAX_PRIORITY = 10;

	public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }
}

通过代码得出结论如下:

  • a、线程的优先级值取值为 [1,10]
  • b、设置的优先级不能大于线程组的最大优先级。如: ThreadGroup maxPriority=7,那么线程设定的 priority 只能到 7

# 五、getId:获取线程ID

作用:获取线程的唯一ID,该 ID 在整个 JVM 进程中是唯一的。

# 通过源码看看线程ID生成规则

public class Thread implements Runnable {
	/* For generating thread ID */
    private static long threadSeqNumber;

	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
		......
		// 设置线程 ID
		tid = nextThreadID();
	}

	private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
}

从代码能够知道,线程 ID 的生成规则:从0开始,以此递增。

# 六、currentThread:获取当前线程

作用:返回当前执行线程的引用

public class Thread implements Runnable {
	public static native Thread currentThread();
}

# 七、interrupt:中断线程

作用:打断当前线程的阻塞状态。

如下方法,会使得线程阻塞,通过 interrupt 能够中断这种阻塞

  • Object 的 wait 和 wait(long) 方法
  • Thread 的 sleep(long) 和 sleep(long,int) 方法
  • Thread 的 join(long) 和 join(long,int) 方法
  • InterruptibleChaanel 的 io 操作
  • Selector 的 wakeup 方法
  • 其他方法

一旦线程在阻塞的状态下被打断,都会抛出一个 InterruptedException 的异常。

小贴士

1、线程被打断,并不意味着,线程的生命周期结束了。

2、对一个生命周期结束的线程, interrupt 方法并不起作用。

# 阻塞的线程被中断后,中断标志会被复位

在 Thread 内维护一个中断标识,在线程调用中断时,该标志为被设定为 true,一个简单的案例如下:

上述案例中的被中断的线程并没有被阻塞,所以被中断之后,标志位总为 true。

小贴士

如果调用的是 interrupted 标志位会被复位,参考:isinterrupted-对比-interrupted

案例:在上述代码的线程中加入阻塞,然后对阻塞进行中断

通过代码和控制台打印能够知道,阻塞方法进行中断捕获处理后,中断标志位会被复位为 false。

# 八、join

作用:以 main 线程起一个 A 线程为例。调用 A.join ,此时 main 线程会被阻塞直到 A 线程的生命周期结束。或者等待时间结束。

简单案例

public class SimpleThread {
    public static void main(String[] args) throws InterruptedException {
        // 创建线程
        List<Thread> threads = IntStream.rangeClosed(1, 2).mapToObj(name -> {
            return new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "#" + i);
                    try {
                        TimeUnit.MILLISECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, name + "");
        }).collect(Collectors.toList());
        threads.forEach(Thread::start); // 启动线程
        for (Thread thread : threads) { // join
            thread.join();
        }

        for(int i = 0;i < 5;i++){
            System.out.println(Thread.currentThread().getName() + "#" +i);
        }
    }
}

控制台打印

2#0
1#0
2#1
1#1
1#2
2#2
1#3
2#3
1#4
2#4
main#0
main#1
main#2
main#3
main#4

通过案例能够验证,main 下的线程 1、2 调用 join 之后,main 会等到他们结束生命周期才会接着执行 main 线程的内容

join(long) 在等待一定时长之后,便不再阻塞,

精彩内容推送,请关注公众号!
最近更新时间: 4/16/2020, 8:37:50 PM