Java Thread 类学习-1

在上一篇博客中介绍了 由 Object 提供的 线程通知与等待 功能.

这篇博客, 探索一下 Thread 提供的控制线程(运行)的方法.

1 汇合子线程结果的 join 方法

单词 join 翻译为中文是 “结合, 汇合, 连接, 参加…” 的意思;

Thread 里的 join 的含义就是 汇合 的意思. 主线程 调用了 子线程的 join 方法后, 主线程会阻塞直到 子线程执行完毕; 这可以理解为 主线程要汇合子线程的结果.

Thread 类中提供了 3 个 join 方法, 方法签名如下:

// 等待 [millis 毫秒] 后返回
public final synchronized void join(long millis)
    throws InterruptedException
    
// 等待 [millis 毫秒 + nanos 纳秒] 后返回
public final synchronized void join(long millis, int nanos)
    throws InterruptedException 

// 没有过期时间, 直到线程执行完才会返回, 或者线程被中断
public final void join() throws InterruptedException

通过 join 系列的源码可知, 它们是通过 Object.wait 方法实现的.

看个示例, 加深理解:

public static void main(String[] args)
        throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread_1 has died.");
    });

    Thread t2 = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread_2 has died.");
    });

    t1.start();
    t2.start();

    System.out.println("wait child threads die.");
    t1.join();
    t2.join();
    System.out.println("done.");
}

上面程序运行结果:

wait child threads die.
thread_1 has died.
thread_2 has died.
done.

在主线程中调用了 两个子线程的 join 方法, 所以在主线程 结束(done) 之前, 两个子线程必定是已经结束了. (换句话说, 主线程没有 join子线程的结果 , 会一直被阻塞, 不会向后执行)

备注:
“等待子线程执行完后, 主线程再执行” 我们一般会使用 CountDownLatch. 后面再介绍 JUC 下的线程同步工具(CountDownLatch, CyclicBarrier, Semaphore).

2 让线程睡眠的 sleep 方法

Thread 类中有两个 静态的 sleep 方法, 当 一个执行中的线程 调用了 Thread.sleep 后, 调用线程会暂时让出指定时间的 执行权(即, 在这段时间内不参与 CPU 的调度), 但是该线程所拥有的 监视器(锁)资源还是持有不让出的(The thread does not lose ownership of any monitors).

sleep 方法签名:

public static native void sleep(long millis) throws InterruptedException;

public static void sleep(long millis, int nanos)
    throws InterruptedException

下面举例说明, 线程 sleep 时不会释放所拥有的监视器资源.

public class Test {
    private static final Lock lock = new ReentrantLock();

    public static void main(String[] args)
            throws InterruptedException {
        Thread t1 = new Thread(() -> {
            lock.lock(); // 加锁
            try {
                System.out.println("thread_1 sleep.");
                Thread.sleep(10000);
                System.out.println("thread_1 wake up.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); // 解锁(释放锁)
            }
        });

        Thread t2 = new Thread(() -> {
            lock.lock(); // 加锁
            try {
                System.out.println("thread_2 sleep.");
                Thread.sleep(10000);
                System.out.println("thread_2 wake up.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); // 解锁(释放锁)
            }
        });

        t1.start();
        t2.start();
    }
}

等待漫长的20秒, 终于执行完了:

thread_1 sleep.
thread_1 wake up.
thread_2 sleep.
thread_2 wake up.

从结果可以看出, thread_1 sleep 了但是没有释放锁, thread_2 在 thread_1 释放锁之前, 一直被阻塞, 直到 thread_1 执行完并释放了锁后, thread_2 才开始执行.

3 让出 CPU 执行权的 yield 方法

单词 yield 作动词有 “放弃; 缴出” 的意思.

Thread 中的 yield 方法名的含义就是 “缴出” CPU.

yield 方法签名:

/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();

上面的英文注释翻译如下:
向调度器(scheduler)发出的提示(hint), 表示当前线程愿意 让出处理器. 调度器可以忽略此提示.
yield 是一种启发式尝试, 旨在改善线程之间的相对进度(progression), 否则会过度使用CPU. 它的使用应该与 详细的分析 和 基准测试(benchmarking) 相结合, 以确保它实际有预期的效果.
使用 yield 方法很少是合适的.
它可能对调试或测试有用, 因为它可能有助于重现由于竞态条件(race condition)而产生的 bug.
在设计并发控制结构时, 比如 java.util.concurrent.locks, 它可能也很有用.

4 线程中断

Java 中的线程中断 是一种 线程间的协作模式, 通过设置线程的 中断标志 并不能直接终止线程的执行, 而是被中断的线程根据 中断状态 自行处理.

interrupt 方法签名:

// 中断线程
// 例如, 当 线程A 运行时, 线程B 可以调用 A 的 interrupt 方法设置中断标志为 true 并立即返回.
// 设置标志并不会中断 A, A 会继续往下执行. 
// 如果 A 因为调用了 wait/join/sleep 而被阻塞挂起,
// 这时 B 调用 A 的 interrupt 方法, A 会在 wait/join/sleep 的地方抛出 InterruptedException 而返回
public void interrupt()

// 检查当前线程是否被中断, 被中断则返回 true.
// 如果线程被中断, 此方法会清除线程的中断标志.
// 注意: 这是一个静态方法
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

// 检查当前线程是否被中断, 被中断则返回 true.
public boolean isInterrupted() {
    return isInterrupted(false);
}

// 上面的 interrupted 和 isInterrupted 方法都是通过此方法实现的
private native boolean isInterrupted(boolean ClearInterrupted);

5 守护线程与用户线程

Java 中的线程分为两类:

  1. daemon 线程(守护线程),
  2. user 线程(用户线程).

在 JVM 启动时会调用 main 函数, main 函数所在的线程就是一个 用户线程, 其实在 JVM 内部同时还启动了很多 守护线程, 比如垃圾回收线程.

当最后一个 非守护线程 结束时, JVM 会正常退出.
守护线程 是否结束并不影响 JVM 退出, 而只要有一个 用户线程 还没结束, 正常情况下 JVM 就不会退出.

main 线程运行结束后, JVM 会自动启动一个 DestroyJavaVM 的线程, 该线会等待所有用户线程结束后终止 JVM 进程.

可以通过 Thread 类提供的 setDaemon(boolean) 方法把用户线程设置为守护线程. 可以通过 isDaemon 方法判断一个线程是否是守护线程.

Reference

[1]. Java 并发编程之美-翟陆续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值