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

本文介绍了Java线程的生命周期,包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED六种状态,并通过代码示例展示了线程在不同状态间的转换。同时,详细讲解了wait、notify、notifyAll、sleep和join等线程控制方法的使用及原理,以及它们在生产者-消费者模式中的应用。
摘要由CSDN通过智能技术生成

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

生命周期

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

  • NEW
    尚未启动的线程处于此状态。
  • RUNNABLE
    在Java虚拟机中执行的线程处于此状态。
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态。
  • WAITING
    正在等待另一个线程执行特定动作的线程处于此状态。
  • TIMED_WAITING
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED
    已退出的线程处于此状态。

一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。

如图示所见

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MwwxQ9DG-1684475551693)(null)]

代码演示

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锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3seWziw5-1684475551650)(null)]

  • 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释放当前锁对象,另一个对象拿到锁之后进行生产或消费
图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I5eDd3zx-1684475551604)(null)]

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() 方法来控制线程的执行顺序,而应使用更可靠的线程同步机制来实现需要的线程协作和同步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值