线程的生命周期和常用办法

生命周期

根据jdk官方文档,线程状况有以下几种

  • NEW 没有发动的线程处于此状况。
  • RUNNABLEJava虚拟机中履行的线程处于此状况。
  • BLOCKED 被堵塞等候监视器确定的线程处于此状况。
  • WAITING 正在等候另一个线程履行特定动作的线程处于此状况。
  • TIMED_WAITING 正在等候另一个线程履行动作到达指定等候时刻的线程处于此状况。
  • TERMINATED 已退出的线程处于此状况。

一个线程能够在给定时刻点处于一个状况。 这些状况是不反映任何操作系统线程状况的虚拟机状况。

如图示所见

线程的生命周期和常用方法

代码演示

NEW / TIMED_WAITING / TERMINATED

package ThreadMethod;
import java.util.concurrent.TimeUnit;
public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        System.out.println("线程状况:"+ thread.getState());
        thread.start();
        Thread.sleep(2000);
        System.out.println("线程状况:" + thread.getState());
        Thread.sleep(2000);
        System.out.println("线程状况:" + thread.getState());
    }
}
成果
线程状况:NEW
线程状况:TIMED_WAITING
线程状况:TERMINATED
线程状况:TERMINATED

WAITING / BLOCKED

package ThreadMethod;
public class BlockThreadState implements Runnable {
    static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        BlockThreadState blockThread = new BlockThreadState();
        Thread thread = new Thread(blockThread);
        System.out.println("thread state : " + thread.getState());
        thread.start();
        Thread.sleep(20);
        System.out.println("thread state : " + thread.getState());
        synchronized (lock){
            lock.notify();
        }
        System.out.println("thread state : " + thread.getState());
        Thread.sleep(20);
        System.out.println("thread state : " + thread.getState());
    }
    @Override
    public void run() {
        try {
            synchronized (lock){
				// wait状况
                lock.wait();
                // synchronized重新拿到锁 处于block状况
                for (int i = 0; i < 1000000000; i++) {
                    continue;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
成果
thread state : NEW
thread state : WAITING
thread state : BLOCKED
thread state : TERMINATED

常见办法

wait()

wait()办法履行后,会堵塞线程,同时开释锁,假如想要唤醒该线程,则需求以下条件

  • 另一个线程调用这个目标的notify()办法,且刚好被唤醒的时本线程
  • 另一个线程调用了这个目标的notifyAll()办法
  • 过了wait(long timeout) 规定的超时时刻,假如传入0便是永久等候
  • 线程本身调用了interrupt()

PS: 运用wait()办法时,必须先拥有monitor锁, 也便是说wait办法需求放在同步代码块中履行

notify/notifyAll

notify/notifyAll用于唤醒线程,当另一个线程调用wait()进入 waitting状况时,另一个线程调用notifyAll()可唤醒当时线程(假如有多个线程,运用notify并不一定能够唤醒线程)

组合运用示例

public class BlockThread {
    public static void main(String[] args) {
        Message message = new Message();
        // 创建一个等候线程
        Thread waitThread = new Thread(new WaitThread(message));
        // 创建一个唤醒线程
        Thread notifyThread = new Thread(new NotifyThread(message));
        // 发动等候线程和唤醒线程
        waitThread.start();
        notifyThread.start();
    }
}
// 同享的音讯类
class Message {
    private boolean isReady = false;
    // 等候办法
    public synchronized void waitForMessage() {
        while (!isReady) {
            try {
                // 当音讯不行用时,线程进入等候状况
                System.out.println("线程进入等候状况");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 履行到这儿表明收到音讯,进行处理
        System.out.println("收到音讯!");
    }
    // 唤醒办法
    public synchronized void sendMessage() {
        // 做一些准备工作,例如获取音讯
        // 唤醒等候的线程
        isReady = true;
        System.out.println("唤醒等候的线程");
        notify();
    }
}
// 等候线程
class WaitThread implements Runnable {
    private Message message;
    public WaitThread(Message message) {
        this.message = message;
    }
    @Override
    public void run() {
        // 等候音讯
        message.waitForMessage();
    }
}
// 唤醒线程
class NotifyThread implements Runnable {
    private Message message;
    public NotifyThread(Message message) {
        this.message = message;
    }
    @Override
    public void run() {
        // 发送音讯
        message.sendMessage();
    }
}
/*
线程进入等候状况
唤醒等候的线程
收到音讯!
Process finished with exit code 0
*/

图示 monitor锁

线程的生命周期和常用方法

  • Entry Set 入口集
    • 线程进入后抢锁
  • The owner 锁持有线程
    • 假如办法没有履行完之前没有开释锁,则程序正常退出并开释锁
    • 假如办法没有履行完之前开释了锁如调用了wait()办法,则程序再次进入等候集进行抢锁
  • Wait Set 等候集
    • 假如上方入口集,不同点是等候集在履行办法锁时中途开释了锁

wait、notify 完成出产者消费者模式

import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
public class ProductAndConsumer {
    List<Object> container = new LinkedList<>();
    public static void main(String[] args) {
        ProductAndConsumer productAndConsumer = new ProductAndConsumer();
        Thread thread1 = new Thread(new Product(productAndConsumer));
        Thread thread2 = new Thread(new Consumer(productAndConsumer));
        thread1.start();
        thread2.start();
    }
    public synchronized void addObject() throws InterruptedException {
        if (container.size() >=100){
            wait();
        }
        container.add(new Object());
        System.out.println("正在出产第"+ container.size() + "个");
        notify();
    }
    public synchronized void conObject() throws InterruptedException {
        if (container.size() == 0){
            wait();
        }
        container.remove(0);
        System.out.println("正在消费第"+ container.size() + "个");
        notify();
    }
}
class Product implements Runnable{
    private final ProductAndConsumer productAndConsumer;
    Product(ProductAndConsumer productAndConsumer){
        this.productAndConsumer = productAndConsumer;
    }
    @Override
    public void run() {
       while (true){
           try {
               productAndConsumer.addObject();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}
class Consumer implements Runnable{
    private final ProductAndConsumer productAndConsumer;
    Consumer(ProductAndConsumer productAndConsumer){
        this.productAndConsumer = productAndConsumer;
    }
    @Override
    public void run() {
       while (true){
           try {
               productAndConsumer.conObject();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}

解说

  • 运用类ProductAndConsumer作为锁目标,container作为容器
  • wait notify来进行线程之间的通讯,在数量满足条件时运用wait开释当时锁目标,另一个目标拿到锁之后进行出产或消费

图示

线程的生命周期和常用方法

sleep()

Thread.sleep()是Java中一个静态native办法,用于使当时线程进入休眠状况(暂停履行)一段指定的时刻。

它的办法签名为:

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

参数millis表明线程休眠的时刻,以毫秒为单位。传入的值是一个正整数,表明线程要休眠的毫秒数。注意,该办法会抛出InterruptedException反常,由于线程在休眠期间或许被其他线程中止。

特点和用法

  • 线程堵塞:调用Thread.sleep()办法会导致当时线程暂停履行,进入堵塞状况。在指定的时刻内,线程不会进行任何操作。
  • 时刻精度:传入的休眠时刻是以毫秒为单位,但实际的休眠时刻或许会稍长或稍短。详细的精度取决于底层操作系统和JVM的完成。
  • 中止呼应:假如在线程休眠期间,另一个线程中止了正在休眠的线程,Thread.sleep()办法会抛出InterruptedException反常。能够在catch块中处理该反常,或许将反常持续向上抛出。
  • 不会开释锁Thread.sleep()办法会暂停当时线程的履行,但不会开释任何锁。假如线程在履行同步代码块或同步办法时调用了Thread.sleep(),其他线程仍无法取得该锁。
  • 静态办法Thread.sleep()是一个静态办法,能够直接经过Thread类调用,无需创建线程目标。
  • 用处:常见的用处包含模拟推迟、定时任务、操控线程履行次序等。

Thread.sleepTimeUnit比较

  • 精度和可读性: Thread.sleep()的参数是以毫秒为单位的时刻值,表明线程要休眠的时刻。而TimeUnit供给了更高层次的时刻单位,如TimeUnit.SECONDS表明秒,TimeUnit.MILLISECONDS表明毫秒等。运用TimeUnit能够使代码更具可读性,而不需求手动计算毫秒数。
  • 反常处理: Thread.sleep()办法会抛出InterruptedException反常,由于线程在休眠期间或许会被其他线程中止。而TimeUnit方式不会直接抛出反常,需求开发者手动处理中止情况。
  • 静态与非静态: Thread.sleep()Thread类的静态办法,能够直接经过类名调用。而TimeUnit是一个枚举类,需求经过详细的枚举常量来调用其办法,例如TimeUnit.SECONDS.sleep(1)
  • 可读性和易用性: 运用TimeUnit能够进步代码的可读性,由于能够直观地表明时刻单位。此外,TimeUnit还供给了其他办法,如TimeUnit.toMillis()TimeUnit.toSeconds()等,便利进行时刻单位之间的转换。
TimeUnit源码

以下是截取部分源码

public enum TimeUnit {
      /**
     * Performs a {@link Thread#sleep(long, int) Thread.sleep} using
     * this time unit.
     * This is a convenience method that converts time arguments into the
     * form required by the {@code Thread.sleep} method.
     *
     * @param timeout the minimum time to sleep. If less than
     * or equal to zero, do not sleep at all.
     * @throws InterruptedException if interrupted while sleeping
     */
    public void sleep(long timeout) throws InterruptedException {
        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }
}

能够看到TimeUnit底层还是调用了Thread.sleep() 有一个比较隐含的地便利是当运用过TimeUnitsleep办法时,假如传入的时刻小所以不会进入if判别,而Thread.sleep()办法假如传参小于0则会抛出反常(源码见下)

public static void sleep(long millis, int nanos)
throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}

join()

Thread.join()是Java中的一个办法,用于等候调用该办法的线程履行结束。它的作用是让当时线程等候指定线程履行结束,然后再持续履行当时线程的后续代码。

简单来说便是堵塞主线程。

简单示例

package ThreadMethod;
public class JoinMethod {
    public static void main(String[] args) {
        Thread thread1 =  new Thread(()->{
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程履行结束");
        },"子线程");
        thread1.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程履行结束");
    }
}
成果
子线程履行结束
主线程履行结束

能够看到子线程等候了3秒,但是终究还是子线程先履行结束在履行主线程打印,原因是由于thread1.join 对主线程进行了堵塞,这是主线程需求等子线程履行结束才会履行后面的语句

特点和注意事项

  • 等候履行: 调用join()办法的线程将会等候指定线程履行结束。假如指定线程已经履行结束,则join()办法会立即返回。
  • 堵塞调用线程: 在调用join()办法期间,当时线程将会被堵塞,暂停履行。只有当指定线程履行结束后,当时线程才会解除堵塞,持续履行。
  • 反常处理: join()办法会抛出InterruptedException反常,由于在等候过程中,当时线程或许会被中止。能够在catch块中处理该反常,或将反常持续向上抛出。
  • 次序履行: 经过运用join()办法,能够操控线程的履行次序。调用join()办法后,当时线程会等候指定线程履行结束,然后再持续履行后续代码。
  • 调用目标: join()办法是一个实例办法,需求经过线程目标调用。例如,假如thread1是一个Thread目标,能够运用thread1.join()来等候thread1履行结束。

源码和底层完成

以下截取部分Thread.join源码

// 入口
public final void join() throws InterruptedException {
    join(0);
}
// 调用的join(0)
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
// 实际调用的本地native办法  wait
public final native void wait(long timeout) throws InterruptedException;

能够看到join的底层还是运用wait办法完成的,子线程调用wait办法让主线程进入等候状况,在运行结束后自动调用notify办法唤醒主线程。(详细唤醒的办法在jvm中)

有一点疑问我没有找到解说:为什么调用子线程的wait(0)办法,堵塞的确是主线程呢?

CountDownLatchCyclicBarrier

运用countDownLatchCyclicBarrier也能够完成线程之间的堵塞,详细暂不讨论

yeild() (让步)

yield() 是一个静态办法,它属于 Thread 类,用于提示调度器将当时线程让出 CPU 的履行权,使得其他具有相同优先级的线程有时机履行。

yield() 办法的调用并不能确保一定会使其他线程取得履行时机,它仅是一个提示。详细的调度行为取决于操作系统和 JVM 的完成。因此,在实际应用中,不该过度依赖 yield() 办法来操控线程的履行次序,而应运用更牢靠的线程同步机制来完成需求的线程协作和同步。