何为进程?

  • 进程就是系统中正在运行的程序,程序一旦运行就是进程。
  • 每个进程都拥有自己独立的地址空间
  • 一个进程无法访问另外一个进程的变量和数据结构,如需访问需要通过进程之间的通信,比如管道,文件和套接字

何为线程?

  • 操作系统中能够进行调度的最小单位,线程包含的进程里
  • 线程是进程运行的最小单位
  • 线程是进程中一个单一顺序运行的控制流,一个进程可以并发多个线程,每个线程并行执行不同的任务

线程的实现方式

  1. 继承Thread

通过继承 Thread 类创建线程,主要是重写其 run() 方法,将需要在线程中执行的任务写入该方法。

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is running...." + Thread.currentThread().getName());
    }
}
  • 特点:

    • 实现起来简单直接,但是无法继承其他类(Java不支持多继承)
    • 不适合资源共享,因为每次创建都需要新的实例
  1. 实现Runnable接口

实现 Runnable 接口是更常用的方式,尤其在需要多个线程共享资源时。

// 实现方式1
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread is running..." + Thread.currentThread().getName());
    }
}

// 实现方式2
public class ThreadCreateExample {
    public static void main(String[] args) {
        Thread th = new Thread(()
                -> System.out.println("Thread is running..." + Thread.currentThread().getName()));
        th.start();
    }
}

特点:

  • 更加灵活,可以实现多个接口
  • 更适合资源共享,因为可以将同一个Runnable实例传递给多个Thread
  1. 实现Callable接口

Callable 是 Java 5 引入的新方式(在 java.util.concurrent 包中),与 Runnable 的区别在于:

  • 他可以返回结果
  • 他可以抛出异常
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Callable task executed:" + Thread.currentThread().getName();
    }
}

public class ThreadCreateExample {
    public static void main(String[] args) throws Exception {
        Callable<String> callable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();

        String result = futureTask.get();
        System.out.println(result);
    }
}

特点

  • 能够返回结果和处理异常。
  • 更适合需要获取线程任务执行结果的场景。
  1. 使用线程池
    Java 提供了线程池来管理和复用线程,这是一种更高效的方式,特别是在需要频繁创建线程的场景中。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交任务
        for (int i = 0; i < 5; i++) {
            executorService.submit(() -> {
                System.out.println("Thread pool task: " + Thread.currentThread().getName());
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

特点

  • 线程池能够高效管理线程资源,减少线程频繁创建和销毁的开销。
  • 适合并发量较大的场景。

Thread类 Run和Start的区别

start()的作用

  • 流程

    • start会启动一个新的线程
    • 它会调用底层的本地方法,让JVM为线程分配资源,并最终调用run()方法
  • 关键点:

    • 调用start()方法,线程会进入就绪状态,等待CPU调度
    • 当被调度时,线程会执行run()方法中的代码
  • 特点:

    • start()是异步调用,主线程和新线程可以同时运行。
    • 新线程由 JVM 调度,run() 方法在新的线程中运行

run()的作用

  • 解释

    • run()方法是一个普通的方法,定义线程的任务逻辑。
    • 如果直接调用 run() 方法,不会创建新线程,而是由当前线程执行任务,相当于普通方法调用
  • 特点:

    • run() 是同步调用,它不会启动新线程,线程任务由当前线程(这里是主线程 main)执行。
    • 没有真正的多线程效果。

线程的状态

线程的六种状态

  1. NEW(新建状态)
  • 定义:线程被创建但尚未启动时的状态
  • 触发条件:调用 new Thread() 创建线程对象后,线程处于 NEW 状态。
  • 特点:线程还未分配任何系统资源。
  1. RUNNABLE(可运行状态)
  • 定义:线程已经启动,并处于可运行状态。
  • 触发条件:调用 start() 方法后,线程进入 就绪队列,等待 CPU 调度。
  • 特点:此状态并不意味着线程正在运行,只是具备运行条件。
  1. BLOCKED(阻塞状态)
  • 定义:线程试图获取一个被其他线程持有的 同步锁 时的状态。
  • 触发条件:线程等待进入同步块或方法。
  • 特点:线程处于挂起状态,无法继续执行。
  1. WAITING(无限等待状态)
  • 定义:线程等待另一个线程显式唤醒。
  • 触发条件:

    • Object.wait()
    • Thread.join()并未设置超时时间
  • 特点:线程处于无限期等待,需要外部唤醒。
  1. TIMED_WAITING(限时等待状态)
  • 定义:线程等待另一个线程显式唤醒,但是有时间限制
  • 触发条件:

    • Thread.sleep()
    • Object.wait(timeout)
    • Thread.join(timeout)
  • 特点:线程在超时时间后会自动从等待状态返回
  1. TERMINATED(终止状态)
  • 定义:线程完成任务或因异常退出时的状态。
  • 触发条件:线程的 run() 或 call() 方法执行结束。
  • 特点:线程进入此状态后不能再次启动。

如何优雅的停止线程

  1. 使用标记位
class StopThreadExample implements Runnable {
    private volatile boolean running = true;

    @Override
    public void run() {
        while (running) {
            System.out.println("Thread is running...");
            try {
                Thread.sleep(1000); // 模拟工作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 重新设置中断状态
                System.out.println("Thread interrupted");
            }
        }
        System.out.println("Thread is stopping...");
    }

    public void stop() {
        running = false;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        StopThreadExample example = new StopThreadExample();
        Thread thread = new Thread(example);
        thread.start();

        Thread.sleep(5000); // 运行 5 秒
        example.stop(); // 停止线程
    }
}

优点:

  • 安全且符合 Java 编程规范。
  • 线程可以正常释放资源并完成必要的清理操作。
  1. 使用 Thread.interrupt()

Thread.interrupt() 是一种常用的线程停止方法,它向目标线程发送中断信号,但不会强制终止线程的执行。

如何处理中断:

  • 如果线程在调用阻塞方法(如 sleep()、wait()、join())时被中断,会抛出 InterruptedException
  • 如果线程未阻塞,Thread.interrupt() 只是将线程的中断状态设置为 true。
class InterruptExample implements Runnable {
    @Override
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("Thread is running...");
                Thread.sleep(1000); // 模拟工作
            }
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted");
            Thread.currentThread().interrupt(); // 重新设置中断状态
        }
        System.out.println("Thread is stopping...");
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptExample());
        thread.start();

        Thread.sleep(5000); // 运行 5 秒
        thread.interrupt(); // 发送中断信号
    }
}

优点:

  • 提供了清理资源的机会。
  • 避免了直接强制终止线程导致的不一致性问题。
  1. 使用 stop() 方法(不推荐)

Thread.stop() 方法会立即终止线程的执行,不管线程的当前状态如何。

为什么不推荐:

  • 它会直接释放线程持有的所有锁,可能导致共享资源的不一致性或数据损坏。
  • 被废弃(deprecated)并不安全。
  1. 使用 ExecutorService.shutdown() 或 shutdownNow()

在使用线程池时,推荐通过线程池的管理方法来停止线程。

方法介绍:

  • shutdown():平滑关闭线程池,允许已提交的任务执行完成。
  • shutdownNow():尝试停止线程池中的所有任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        executor.submit(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("Thread is running...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 恢复中断状态
                    System.out.println("Thread interrupted");
                }
            }
        });

        Thread.sleep(5000); // 运行 5 秒
        executor.shutdownNow(); // 尝试立即停止线程
    }
}

interrupted 和 isInterrupted

  1. interrupted

    • 作用:检测当前线程 的中断状态,并清除中断标志

      • 返回值:返回当前线程的中断状态(true 表示线程已被中断)。
      • 清除中断标志:调用此方法后,中断标志会被清除(从 true 变为 false)。
      • 静态方法:属于 Thread 类的静态方法,作用于当前线程
    • 特点:

      • 只检查当前线程的中断状态,与调用此方法的线程无关
      • 清除中断标志,第二次调用时返回 false
  2. isInterrupted

    • 作用:检测某个线程的中断状态,但不会清中断标志

      • 返回值:返回调用此方法的线程的中断状态(true 表示线程已被中断)。
      • 非静态方法:需要通过线程对象调用,作用于指定线程。
    • 特点

      • 检查指定线程的中断状态,不会清除中断标志。
      • 通常配合业务逻辑,用于让线程根据中断状态优雅退出。
  3. 如何选择?

    • 如果需要 检测并清除 当前线程的中断状态,使用 interrupted()
    • 如果需要 仅检测 某个线程的中断状态,不改变其状态,使用 isInterrupted()

yield

yield 是 Thread 类中的一个静态方法,用于让当前线程提示调度器暂停执行,以便其他线程有机会运行,但它不保证一定会暂停或让其他线程运行。

  1. yield 的工作原理

    • 暂停当前线程:调用 Thread.yield() 后,当前线程会从 运行状态 (RUNNING) 切换到 就绪状态 (READY),等待再次被调度执行。
    • 调度器决定下一步:调度器根据线程优先级和调度策略决定是否重新执行当前线程,或者切换到其他线程
    • 没有强制效果:调用 yield() 后,当前线程可能立即再次被调度执行
  2. yield 的特点

    1. 提示调度器释放CPU:
      它是一个提示而非强制,具体行为取决于 JVM 和底层操作系统。
    2. 不改变线程状态:
      线程从运行状态切换到就绪状态,不会进入阻塞或等待状态。
    3. 不释放锁资源:
      如果当前线程持有锁资源,调用 yield() 后锁依然被持有。
  3. 注意事项

    1. 不可预测性:
      调用 yield() 并不一定会使其他线程运行,因为线程调度器可能会选择重新调度当前线程。
    2. 依赖操作系统和 JVM:
      不同平台的线程调度策略不同,因此 yield() 的行为可能不一致。
    3. 不适合精确控制:
      不建议使用 yield() 作为线程调度的核心控制方式,更推荐使用 wait/notify 或线程池。
方法 描述 是否释放锁 状态变化
yiled 提示调度器让出 CPU,但不强制 从运行状态切换到就绪状态
sleep 使线程进入休眠指定时间,不主动释放CPU 进去阻塞状态
wait 线程等待,需配合 notify 或 notifyAll 进去等待状态
join 等待另一个线程完成执行 当前线程阻塞

如何理解线程被挂起

  1. 线程挂起的本质:

    • 挂起是线程的一种暂停行为,线程主动或被动地停止运行,等待条件满足后恢复。
    • 挂起的线程不占用 CPU。
  2. 阻塞状态:

    • 被动等待资源,不能执行其他任务。
  3. 等待状态:

    • 主动让出资源,可通过条件触发恢复。
  4. 线程调度关系:

    • 挂起的线程不会影响其他线程的运行,JVM 调度器会切换到其他就绪线程,最大化 CPU 利用率。

守护线程

守护线程是 Java 中一种特殊的线程,其生命周期依赖于用户线程(非守护线程)。当所有用户线程都结束运行时,JVM 会自动终止所有守护线程并退出。

守护线程的应用场景
  1. 垃圾回收:

    • JVM 的垃圾回收线程是守护线程的一种典型实现。
  2. 后台服务:

    • 用于处理日志、监控任务等非关键性工作。
  3. 心跳检测:

    • 用于定期检查某些资源的状态或提供后台支持。

join

在 Java 中,join() 是 Thread 类的一个方法,用于让当前线程等待另一个线程执行完成之后再继续运行。

线程同步:阻塞当前线程,直到调用 join() 的线程完成。
线程协调:确保多线程执行的顺序。例如,当线程 B 需要依赖线程 A 的结果时,可以使用 A.join(),让线程 B 等待线程 A 执行完成。

join() 的方法签名
public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException
public final void join(long millis, int nanos) throws InterruptedException
  1. join():等待直到目标线程执行完成。
  2. join(long millis):等待目标线程最多 millis 毫秒。
  3. join(long millis, int nanos):等待目标线程最多 millis 毫秒加 nanos 纳秒。
join() 的工作原理
  • 调用 join() 的线程进入 WAITING 状态。
  • join() 的目标线程完成后,会唤醒调用 join() 的线程。
  • 其实现依赖于 Object 的 wait() 和 notify() 方法。
join() 与线程状态
  • 调用 join() 的线程会进入 WAITING 或 TIMED_WAITING 状态(取决于是否设置了超时时间)。
  • 目标线程完成后,调用 join() 的线程会重新进入 RUNNABLE 状态。
注意事项
  1. 异常处理:

    * join() 方法会抛出 InterruptedException,因此必须捕获或声明抛出。
    * 如果当前线程在等待期间被中断,会提前退出。
  2. 死锁风险:

    * 如果两个线程互相调用对方的 join(),可能导致死锁:
  3. 性能考虑:

    * 如果线程完成时间过长,调用 join() 的线程会长时间阻塞,可能影响程序的响应性。

爱跑步的猕猴桃
1 声望0 粉丝