Java Thread


一、线程

  • 线程(Thread)是并发编程的基础,也是程序执行的最小单元,它依托进程而存在。
  • 一个进程中可以包含多个线程,多线程可以共享一块内存空间和一组系统资源
  • 因此线程之间的切换,更加节省资源、更加轻量化,也因此被称为轻量级的进程。

1. 线程状态

  • 在 JDK 1.5 之后,以枚举的方式被定义在 Thread 的源码中,总共包含以下 6 个状态:
  1. NEW:新建状态

线程被创建出来,但尚未启动时的线程状态。

  1. RUNNABLE:就绪状态

表示可以运行的线程状态,它可能正在运行,或者是在排队等待操作系统给它分配 CPU 资源。

  1. BLOCKED:阻塞等待锁的线程状态
  1. 表示处于阻塞状态的线程正在等待监视器锁。
  2. 比如等待执行 synchronized 代码块或者使用 synchronized 标记的方法。
  1. WAITING:等待状态
  1. 一个处于等待状态的线程正在等待另一个线程执行某个特定的动作。
  2. 比如一个线程调用了 Object.wait() 方法,那它就在等待另一个线程调用 Object.notify()Object.notifyAll() 方法。
  1. TIMED_WAITING:计时等待状态
  1. 和等待状态(WAITING)类似,它只是多了超时时间。
  2. 比如调用了有超时时间设置的方法 Object.wait(long timeout)Thread.join(long timeout) 等这些方法时,它才会进入此状态。
  1. TERMINATED:终止状态

表示线程已经执行完成。


2. 线程状态源码

public enum State {
    /**
     * 新建状态
     * 尚未启动的线程的线程状态。
     */
    NEW,

    /**
     * 就绪状态
     * 可运行线程的线程状态。可运行线程状态正在Java虚拟机中执行,但它可能等待来自操作系统的其他资源如处理器。
     */
    RUNNABLE,

    /**
     * 阻塞等待锁的线程状态
     * 线程的线程状态被阻塞,等待监视器锁定。处于阻塞状态的线程正在等待监视器锁定输入同步块/方法或调用后重新输入同步块/方法。
     * {@link Object#wait() Object.wait}
     */
    BLOCKED,

    /**
     * 等待状态
     * 等待线程的线程状态。线程由于调用其中之一而处于等待状态
     * 由于调用以下方法之一,线程处于等待状态:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     */
    WAITING,

    /**
     * 计时等待状态
     * 具有指定等待时间的等待线程的线程状态。线程由于调用了以下之一而处于定时等待状态
     * 以下方法具有指定的正等待时间:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * 终止状态
     * 终止线程的线程状态。线程已完成执行。
     */
    TERMINATED;
}

3. 线程工作模式

  • 线程的工作模式:
  1. 首先先要创建线程,并指定线程需要执行的业务方法。
  2. 然后再调用线程的 start() 方法,此时线程就从 NEW(新建状态)变成了 RUNNABLE(就绪状态)
  3. 此时线程会判断要执行的方法中,有没有 synchronized 同步代码块。
  1. 如果有同步代码块,并且其他线程也在使用此锁,那么线程就会变为 BLOCKED(阻塞等待状态)
  1. 当其他线程使用完此锁之后,线程会继续执行剩余的方法。

4. 线程工作流程

  • 线程工作流程:
  1. 当遇到 Object.wait()Thread.join() 方法时,线程会变为 WAITING(等待状态状态)
  2. 如果是带了超时时间的等待方法,那么线程会进入 TIMED_WAITING(计时等待状态)
  3. 当有其他线程执行了 notify()notifyAll() 方法之后,线程被唤醒继续执行剩余的业务方法。
  4. 直到方法执行完成为止,此时整个线程的流程就执行完了。
  • 执行流程如下图所示:
    在这里插入图片描述

二、知识扩展


1. BLOCKEDWAITING 区别

  • 虽然 BLOCKEDWAITING 都有等待的含义,但二者有着本质的区别。
  1. 它们形成状态的调用方法不同。
  1. BLOCKED 可以理解为当前线程还处于活跃状态,只是在阻塞等待其他线程使用完某个锁资源。
  2. WAITING 是因为自身调用了 Object.wait()Thread.join() 又或 LockSupport.park() 而进入等待状态,只能等待其他线程执行某个特定的动作才能被唤醒继续。
  1. 比如当线程因为调用了 Object.wait() 而进入 WAITING 状态之后。
  2. 则需要等待另一个线程执行 Object.notify()Object.notifyAll() 才能被唤醒。

2. start()run() 区别

  • 从源码:
  1. start() 方法属于 Thread 自身的方法,并且使用了 synchronized 来保证线程安全。
  2. run() 方法为 Runnable 的抽象方法,必须由调用类重写此方法,重写的 run() 方法就是此线程要执行的业务方法。
  • 从执行效果:
  1. start() 方法可以开启多线程,让线程从 NEW 状态转换成 RUNNABLE 状态,
  2. run() 方法只是一个普通的方法。

2.1 start() 源码
public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
	// 状态验证,不等于 NEW 的状态会抛出异常
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    // 通知线程组,此线程即将启动
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        	// 不处理任何异常,如果 start0 抛出异常,则它将被传递到调用堆栈上      
        }
    }
}

2.2 run() 源码
// 将要运行什么。
private Runnable target;
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

3. 线程优先级

  • Thread 源码中,和线程优先级相关的有 3 个属性:
  1. 线程的优先级可以理解为:线程抢占 CPU 时间片的概率。
  2. 优先级越高的线程,优先执行的概率就越大,但并不能保证优先级高的线程一定先执行。
// 线程可以拥有的最小优先级
public final static int MIN_PRIORITY = 1;

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

// 线程可以拥有的最大优先级
public final static int MAX_PRIORITY = 10

3.1 Thread.setPriority() 设置优先级
  • Thread.setPriority() 设置优先级。
/**
 * @see        #getPriority
 * @see        #checkAccess()
 * @see        #getThreadGroup()
 * @see        #MAX_PRIORITY
 * @see        #MIN_PRIORITY
 * @see        ThreadGroup#getMaxPriority()
 */
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);
    }
}

4. join() 插入等待

  1. 在一个线程中调用 other.join(),这时候当前线程会让出执行权给 other 线程。
  2. 直到 other 线程执行完或者过了超时时间之后,再继续执行当前线程。
  • join() 源码:
  1. 可以看出 join() 方法底层还是通过 wait() 方法来实现的。
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    
	// 超时时间不能小于 0
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

	// 等于 0 表示无限等待,直到线程执行完为止
    if (millis == 0) {
    	// 判断子线程 (其他线程) 为活跃线程,则一直等待
        while (isAlive()) {
            wait(0);
        }
    } else {
    	// 循环判断
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

4.1 未使用 join()
@Test
public void test33() {
    Thread thread = new Thread(() -> {
        for (int i = 1; i < 6; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程睡眠: " + i + "秒");
        }
    });
    // 开启线程
    thread.start();

    // 主线程执行
    for (int i = 1; i < 4; i++) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程睡眠: " + i + "秒");
    }
}
  • 程序执行结果为:
  1. 从结果可以看出,在未使用 join() 时主子线程会交替执行。
主线程睡眠: 1秒
子线程睡眠: 1秒
主线程睡眠: 2秒
子线程睡眠: 2秒
主线程睡眠: 3秒
子线程睡眠: 3

4.2 使用 join()
  • join() 方法加入。
@Test
public void test333() throws InterruptedException {
    Thread thread = new Thread(() -> {
        for (int i = 1; i < 6; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程睡眠: " + i + "秒");
        }
    });
    // 开启线程
    thread.start();
    // 1. 等待子线程先执行 2 秒钟
    thread.join(2000);

    // 主线程执行
    for (int i = 1; i < 4; i++) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程睡眠: " + i + "秒");
    }
}
  • 程序执行结果为:
  1. 从执行结果可以看出,添加 join() 方法之后,主线程会先等子线程执行 2 秒之后,才继续执行。
子线程睡眠: 1秒
子线程睡眠: 2秒
主线程睡眠: 1// thread.join(2000); 等待 2 秒之后,主线程和子线程再交替执行
子线程睡眠: 3秒
主线程睡眠: 2秒
子线程睡眠: 4秒
主线程睡眠: 3秒
子线程睡眠: 5

5. yield() 愿意出让CPU

  • Thread 的源码 yield() 为本地方法,也就是说 yield() 是由 CC++ 实现的。
  • yield() 源码:
  1. yield() 表示给线程调度器一个当前线程愿意出让 CPU 使用权的暗示,但是线程调度器可能会忽略这个暗示。
public static native void yield();

5.1 使用 yield()
@Test
public void test4() {

    Runnable runnable = new Runnable() {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("线程: " + Thread.currentThread().getName() + i);
                if (i == 5) {
                    Thread.yield();
                }
            }
        }
    };

    new Thread(runnable, "A").start();
    new Thread(runnable, "B").start();
}
  • 程序执行结果为:
  1. 多次执行这段代码之后会发现,每次执行的结果都不相同。
  2. 这是因为 yield() 执行非常不稳定,线程调度器不一定会采纳 yield() 出让 CPU 使用权的建议,从而导致了这样的结果。
线程: B0
线程: A0
线程: A1
线程: A2
线程: A3
线程: A4
线程: A5
线程: B1
线程: B2
线程: B3
线程: B4
线程: B5
线程: A6
线程: A7
线程: A8
线程: A9
线程: B6
线程: B7
线程: B8
线程: B9

三、小结

1. BLOCKED(阻塞等待)和 WAITING(等待)有什么区别?

2. start() 方法和 run() 方法有什么区别?

3. 线程的优先级有什么用?该如何设置?

4. 线程的常用方法有哪些?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑士梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值