【JAVA多线程】基础操作和状态控制

目录

1.如何创建一条线程

2.JDK中的线程状态

3.基础操作

3.1关闭

3.2中断

3.3.等待、唤醒

3.4.阻塞、唤醒

3.5.同步等待


1.如何创建一条线程

创建线程常用的有两种方法:

  • 继承 Thread 类
  • 实现 Runnable 接口
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("MyThread: " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程

        for (int i = 0; i < 5; i++) {
            System.out.println("Main Thread: " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.JDK中的线程状态

在JDK的线程体系中线程一共6种状态:

  • NEW(新建): 当线程对象创建后,但尚未启动时,线程处于新建状态。
  • RUNNABLE(可运行): 当线程调用start()方法后,线程将变为可运行状态。此时线程可能正在运行,也可能正在等待CPU分配时间片。
  • BLOCKED(阻塞): 这个状态通常指线程在等待锁。当多个线程试图同时访问同步块或方法时,没有获得锁的线程将被阻塞。
  • WAITING(等待): 线程进入等待状态是因为调用了Object.wait(), Thread.join(), 或LockSupport.park()等方法。等待状态的线程不会竞争锁,也不会消耗CPU时间。
  • TIMED_WAITING(定时等待): 类似于等待状态,但线程将在一定时间后自动恢复到可运行状态,例如调用Thread.sleep(long millis)或wait(long timeout)。
  • TERMINATED(终止): 线程执行完毕或因异常而终止,将进入终止状态。

线程状态之间的转换如下:

  • NEW -> RUNNABLE: 调用线程的start()方法后发生。
  • RUNNABLE -> BLOCKED: 当线程尝试获取一个已被其他线程持有的锁时发生。
  • RUNNABLE -> WAITING/TIMED_WAITING: 当线程调用等待方法时发生。
  • BLOCKED -> RUNNABLE: 当线程获得了锁,或者锁被释放时发生。
  • WAITING/TIMED_WAITING -> RUNNABLE: 当等待条件满足,如其他线程调用了notify()或notifyAll(),或者定时等待时间到期时发生。
  • RUNNABLE -> TERMINATED: 当线程的run()方法完成或抛出未捕获的异常时发生。

其中需要值得重点关注的是等待(wait、timed_waiting)和阻塞(blocked):

  • 等待,线程不往下执行,不让出cpu资源

  • 阻塞,线程不往下执行,让出cpu资源。

其实在操作系统中线程一共就三大类状态,运行、阻塞、就绪,JDK中的等待和阻塞都归为阻塞这一大类状态中,等待又叫轻量级阻塞,阻塞又叫重量级阻塞。当在JDK的多线程体系里有大量的地方会用到等待/唤醒、阻塞/唤醒的两套api:

  • wait()阻塞线程、notify()唤醒线程

  • usfe.park()阻塞线程、usfe.unpark()唤醒线程

3.基础操作

3.1关闭

JAVA提供stop()、destory()两个函数来强制杀死线程,但是这两个方法已经被废弃,因为它们可能导致数据不一致和其他严重的副作用,比如资源泄漏。Thread.stop()方法会立即停止线程,并且会抛出ThreadDeath错误,这可能会导致线程在未清理资源的情况下突然终止。

推荐使用以下两种方式进行线程的关闭:

  • 守护线程,守护线程是一种特殊的线程类型,它不会阻止JVM的关闭。当JVM中不再有非守护线程运行时,JVM会自动退出,即使还有守护线程在运行。

  • 标志位,使用标志位是一种常见的线程终止策略,线程会在每次循环中检查标志位的状态,如果标志位被设置为true,则线程会退出循环并终止。

守护线程代码示例:

public class DaemonThreadExample {

    public static void main(String[] args) throws InterruptedException {
        Thread daemonThread = new Thread(() -> {
            while (true) {
                System.out.println("Daemon thread running...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.out.println("Daemon thread interrupted.");
                    break;
                }
            }
        });
        daemonThread.setDaemon(true); // 设置为守护线程
        daemonThread.start();

        // 主线程休眠一段时间后退出,此时守护线程也会随之退出
        Thread.sleep(5000);
    }
}

标志位代码示例:

public class FlagBasedTermination {

    private volatile boolean stopRequested = false;

    public void requestStop() {
        this.stopRequested = true;
    }

    public void runTask() {
        while (!stopRequested) {
            // 执行任务...
            System.out.println("Task running...");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Task interrupted.");
                break;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        FlagBasedTermination task = new FlagBasedTermination();
        Thread taskThread = new Thread(task::runTask);
        taskThread.start();

        // 主线程休眠一段时间后请求停止子线程
        Thread.sleep(5000);
        task.requestStop();
    }
}

3.2中断

中断,不是指中断线程,而是指中断阻塞,唤醒线程。只有轻量级阻塞能够被中断,重量级阻塞不能被中断。

  • 轻量级阻塞,即等待状态,对应的JAVA线程中的状态有:

    • WAITING

    • TIMED_WAITING

  • 重量级阻塞,即阻塞状态,对应的JAVA线程中的状态有:

    • BLOCKED

JAVA提供了两个与中断阻塞相关的函数:

  • t.isInterrupted()

    非静态函数,读取中断标志位,不重置中断标志位。

  • Thread.interrupted()

    静态函数,给线程发送一个唤醒信号,如果是处于轻量级阻塞的线程收到唤醒信号后会被唤醒,重置中断标志位,并且抛出InterruptedException。

3.3.等待、唤醒

线程的等待状态是指线程由于一些原因先不向下执行,但暂时不放弃资源,只是稍作等待而已。等待和阻塞不同,阻塞是本来就不该执行,所以等待是可以发生在任何地方的,因此JDK给每个对象都设计了一个监视器锁(Monitor),这是Java虚拟机(JVM)底层为线程同步做出的支持。有了这个Monitor后JDK支持了线程在任何对象上先暂时等待,因为线程可以暂时躺在这个Monitor上。并给Object设计了如下api用来操作线程进行等待和唤醒:

  • wait(),等待。
  • notify(),唤醒。

在使用等待、唤醒时有两点注意事项:

  • 持有锁

    即wait()、notify()需要进行互斥,不可能一边notify(),一边wait()。

    代码示例:

  • 释放锁

    由于wait()是在同步代码块中执行的,wait()时要注意锁的释放,否则会死锁。

代码示例:

public class WaitNotifyDemo {

    private static final Object lock = new Object();
    private static boolean ready = false;

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Producer: Waiting for consumer...");
                lock.notify();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                ready = true;
            }
        });

        Thread consumer = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Consumer: Waiting for notification...");
                    lock.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                if (ready) {
                    System.out.println("Consumer: Received notification, ready is true.");
                }
            }
        });

        consumer.start();
        producer.start();
    }
}

3.4.阻塞、唤醒

阻塞唤醒有工具类:

LockSupport.park(), LockSupport.unpark(Thread thread)

其实底层调的都是线程原语:

JDK操作线程阻塞用的都是这个原语,底层就是用native调的操作系统的接口:

3.5.同步等待

同步等待,Thread.join()即主线程要等待子线程执行完后才能继续执行。以下代码示例中,主线程会等待两条子线程完成后再终止

public class JoinExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread 1: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread 2: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join(); // 等待 thread1 完成
            thread2.join(); // 等待 thread2 完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread continues after both threads have finished.");
    }
}

join方法有三种重载:

  • void join(): 等待该线程终止。
  • void join(long millis): 等待该线程终止,最多等待指定的毫秒数。
  • void join(long millis, int nanos): 等待该线程终止,最多等待指定的毫秒数加上纳秒数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_BugMan

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

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

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

打赏作者

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

抵扣说明:

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

余额充值