Java多线程的故事

2024.09.30 · 8 minute read

线程和进程

Process 进程是 System 级单位,有独立运行空间,切换会有较大开销。

Thread 线程是处理器 CPU 调度单位,同一类共享代码和数据空间,比 Process 轻量,切换的开销小。

一个 Process 是一个 Java 程序的运行,包含至少一个或 N 个 Thread。

创建一个线程

创建一个 Thread,可以直接使用 Thread Class。或者使用以下三种方法自定义:

  • 继承 Thread 类:简单,无法继承。重写 run 方法。
  • 实现 Runnable 接口:复杂,可以继承。重写 run 方法。
  • 实现 Callable 接口:同 Runnable。重写 call 方法。

其中,继承 Thread 类和实现 Runnable 接口,无法获得执行结果,无法处理执行异常。

但是位于 java.util.concurrnet 中的 Callable 解决了该问题,call 方法是 run 方法增强版,可以在实现时,通过传入泛型的方式,定义返回值。

例子:继承 Thread 类

class Scratch extends Thread{
    @Override
    public void run() {
        System.out.println("Thread is Created");
    }
    public static void main(String[] args) {
        Scratch thread = new Scratch();
        thread.start();
        System.out.println(Thread.currentThread().getName());
    }
}

例子:实现 Runnable 接口

class MyTask implements Runnable {
 public void run() {
  System.out.println("Task is running");
 }
}

public class Main {
 public static void main(String[] args) {
  MyTask task = new MyTask();
  Thread thread = new Thread(task);
  thread.start();
 }
}

MyTask 类实现了 Runnable 接口,只定义了可执行的任务,但本身不是线程。Thread 类提供了机制,将任务与线程相互关联。

例子和解读:实现 Callable 接口

接口的定义是:

@FunctionalInterface
public interface Callable<V> {
 V call() throws Exception;
}

补充知识:@FunctionalInterface 是函数式接口,可以用 Lambda 表达实现:ClassWithFunctionalInterface func = () => System.out.println("ok");

Callable 是 Runnable 的补丁,V 定义了返回值,Exception 定义了异常。

因为最终我们使用 Thread 实现多线程,而它只接受 Runnable,因此需要一个机制,连接 Callable 和 Thread。

解决方案是 FutureTask 类。

该类提供两个构造方法:

  • FutureTask(Callable callable),可以和 Callable 关联。
  • FutureTask(Runnable runnable, V result),也支持 Runnable。

该类是 RunnableFuture 接口的实现,源码是:

public interface RunnableFuture<V> extends Runnable, Future<V> {
 void run();
}

Runnable 负责 Thread,Future 负责得到来自 Callable 的返回值。

总结一下逻辑关系是:

Thread -Runnable + Callable - Future -> RunnableFuture -> FutureTask

用代码逻辑表示是:

// Callable
Scratch scratch = new Scratch();
// To FutureTask
FutureTask<String> future = new FutureTask<>(scratch);
// To Thread
Thread thread = new Thread(future);

完整的 FutureTask 使用例子:

class Scratch implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("Scratch");
        return "OK";
    }

    public static void main(String[] args) {
        // Callable
        Scratch scratch = new Scratch();
        // To FutureTask
        FutureTask<String> future = new FutureTask<>(scratch);
        // To Thread
        Thread thread = new Thread(future);
        thread.start();
        try {
            // Get result from FutureTask
            String result = future.get(5, TimeUnit.SECONDS);
            System.out.println(result);
            System.out.println(Thread.currentThread().getName());

        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }
    }
}

线程的不同状态

通过 new,我们创建了一个 Thread 对象。在从生到死的完整生命周期中,它包括六个状态:new,runnable,blocked,waiting,time_waiting,terminated。

具体如下:

  1. New 新建:初识状态,不能运行。JVM 为它分配了内存,没有线程生命特征,和其它 Java 对象一样。
  2. Runnable 可运行:New 状态,调用 start() 进入。它细分成两个状态:
    1. Ready 就绪:它没运行,只是做好准备,等待 JVM 调度。
    2. Running 运行:真正运行。JVM 或 CPU 开始调度。
  3. Blocked 阻塞:因某些原因,失去 CPU 执行权,即 CPU 不执行它。一般会因两种情况进入阻塞:
    1. [[同步锁]] 被其它线程截胡。
    2. 线程运行时,发出 IO 请求。
  4. Waiting 等待:Running 中的线程,调用了 wait()join() 方法,进入该状态。等待中的线程,必须等其他线程执行特定操作后,才有机会再次争夺 CPU 使用权,然后转为 Running。例如:
    • wait():等待其他线程调用 notify()notifyAll() 唤醒
    • join():等待其他加入的线程终止。
  5. Timed_waiting 定时等待:Running 中的线程,调用了 sleep(long millis)wait(long timeout)join(long millis) 等方法进入。类似 Waiting 状态。
  6. Terminated 终止run()call() 执行完或者未捕获的 Exception、错误 Error,线程进入终止状态,生命周期结束。

切换不同状态

New -> Runnable

调用 start() 方法,启动线程,然后会自动调用 run()

单纯调用 run(),无法启动线程,参考下面的例子:

public class StartVsRunExample {
 public static void main(String[] args) {
  // 创建一个实现了 Runnable 接口的匿名类
  Runnable task = new Runnable() {
   @Override
   public void run() {
    for (int i = 0; i < 5; i ++) {
     System.out.println(Thread.currentThread().getName() + ": Count " + i);
     try {
      Thread.sleep(500); // 睡眠500毫秒
     }
     catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   }
  }

  Thread thread1 = new Thread(task, "Thread-1");
  thread1.start();

  Thread thread2 = new Thread(task, "Thread-2");
  thread2.run();

  // 主线程继续执行
  for (int i = 0; i < 5; i++) {
   System.out.println(Thread.currentThread().getName() + ": Main Count " + i);
   try { Thread.sleep(500); }
   catch (InterruptedException e) { e.printStackTrace(); }
  }

 }
}

输出:

main: Count 0
Thread-1: Count 0
Thread-1: Count 1
main: Count 1
main: Count 2
Thread-1: Count 2
main: Count 3
Thread-1: Count 3
Thread-1: Count 4
main: Count 4
main: Main Count 0
main: Main Count 1
main: Main Count 2
main: Main Count 3
main: Main Count 4

其中,main: Count 0 在主线程中运行,不在线程 thread2 中。

Runnable -> Block

例子:

两个线程,抢通过定义 Object 为 lock,:

class BlockThread extends Thread {
    private String name;
    private Object lock;
    public BlockThread(String name, Object lock) {
        this.name = name;
        this.lock = lock;
    }

    @Override
    public void run() {
        System.out.println("Thread " + name + " State is "+Thread.currentThread().getState());
        synchronized (lock) {
            System.out.println("Thread " + name + " hold the lock.");
            try {
                System.out.println("Thread " + name + " State is "+Thread.currentThread().getState());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread " + name + " released the lock.");
        }
    }
}

public class Scratch {

    public static void main(String[] args) throws Exception{
        Object lock = new Object();
        BlockThread t1 = new BlockThread("Thread1", lock);
        BlockThread t2 = new BlockThread("Thread2", lock);
        t1.start();
        t2.start();
        Thread.sleep(100);
        System.out.println("T1 state is "+t1.getState());
        System.out.println("T2 state is "+t2.getState());
    }
}

输出:

Thread Thread2 State is RUNNABLE
Thread Thread1 State is RUNNABLE
Thread Thread2 hold the lock.
Thread Thread2 State is RUNNABLE
T1 state is BLOCKED
T2 state is TIMED_WAITING
Thread Thread2 released the lock.
Thread Thread1 hold the lock.
Thread Thread1 State is RUNNABLE
Thread Thread1 released the lock.

Runnable -> Waiting

join():让其它线程进入自身,使用 wait() 机制实现。

例如,当 Thread A 执行 threadB.join() 时,线程 A 会进入 WAITING 状态,等待 ThreadB 结束后,再继续。

例子:

public class JoinExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("Thread started");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread finished");
        });

        thread.start();
        System.out.println("Main thread waiting for thread to finish");
        thread.join(); // thread join main
        System.out.println("Main thread continues");
    }
}

wait():Object 类方法,每个对象都有,必须在 synchronized block 中使用 ([[同步锁]])。使进程进入等待,直到其他线程,调用同一对象 notify()notifyAll()

使用方法:

synchronized (object) {
 while (条件不满足) {
  object.wait();
 }
 // 条件满足,继续执行
}

它区别 sleep()

  • sleep() 不释放锁,wait() 释放锁。
  • sleep() 暂停执行,wait() 用于进程通信

例子:

class Scratch {

    public static void main(String[] args) {
       SharedResource resource = new SharedResource();

       Thread t1 = new Thread(() -> {
           try {
               resource.use();
           } catch (Exception e) {
               e.printStackTrace();
           }
       });

       Thread t2 = new Thread(() -> {
           try {
               Thread.sleep(2000);
               resource.prepare();
           } catch (Exception e) {
               e.printStackTrace();
           }
       });
    // not ready, waiting...
       t1.start();
       // ready, notify!
       t2.start();
    }
}

class SharedResource {
    private boolean isReady = false;

    public synchronized void prepare() {
        isReady = true;
        notify();
    }

    public synchronized void use() throws InterruptedException{
        while (!isReady) {
            wait();
        }
        System.out.println("I am ready");
        isReady = false;
    }
}

Waiting -> Runnable

notify():通知 wait()

例子见上一子章节。

参考

感谢您的阅读!您的支持是我的动力。

如果您喜欢这篇文章,不妨请我喝杯咖啡。 ☕️