线程
线程的基本概念
并行和并发
- 并行:多个CPU核心同时工作,处理不同的任务。
- 并发:多个任务交替使用 CPU 核心工作,以提高 CPU 利用率。
进程和线程
进程 Processor
程序的一次执行。由操作系统创建并分配资源,执行一个单独的任务。
进程是系统进行资源分配和调度的独立单位,每个进程都有自己的内存空间和系统资源。进程内所有线程共享堆存储空间,保存程序中定义的对象和常量池。
Windows系统中,每个运行的 Java 程序都是一个独立的进程。
线程 Thread
进程内的执行单元,不分配单独的资源,执行一个单独的子任务。
线程是进程内调度和分派的基本单位,共享进程资源。每个线程有自己的独立的栈存储空间,保存线程执行的方法以及基本类型的数据。
运行的 Java 程序内含至少一个主线程 main ,用户可以在 Java 程序中自定义并调用多个线程。 JVM 垃圾回收线程也是一个独立的线程。
线程的运行状态
线程除创建状态 New 和结束状态 Terminate 外,主要有以下几种运行状态:
运行
(Running) CPU 正在执行线程。
就绪
(Runnable) 线程一切就绪,等待 CPU 执行。
运行/就绪状态 统称为可运行状态 Runnable。 Java 程序中,线程在 运行/就绪状态 之间的切换由 JVM 自动调度,开发者无法获知。线程之间的调度采用分优先级多队列时间片轮转算法。进程在执行完 CPU 时间片切换到就绪状态之前会先保存自己的状态,下次进入运行状态时再重新加载。
阻塞
(Blocked) 线程因缺少其他资源,比如请求资源被上锁而暂停执行。在获得资源后进入就绪状态。
等待
(Waitting) 线程接受了等待指令,释放资源暂停执行。在超时/接受唤醒指令后进入就绪状态。
Thread 类
Thread 类是系统自带的线程类,实现了 Runnable 接口。
线程定义
Runnable 接口内唯一声明了 run 方法,由 Thread 类实现。开发者在 run 方法中定义运行时线程将要执行的功能,线程开启后由 JVM 自动调用并执行。如果开发者主动调用 run 方法,只会当作普通方法执行。
开发者可以通过以下两种方式自定义线程类:
- 继承 Thread 类,重写 run 方法。
public class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }Copy to clipboardErrorCopied 复制代码
- 实现 Runnable 接口,实现 run 方法。推荐使用,避免了单继承的局限性。
public class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }Copy to clipboardErrorCopied 复制代码
线程启动
Thread 类定义了 start 方法。调用 start 方法后,系统会开启一个新线程进入就绪状态:由 JVM 会自动对线程进行调度,在运行时调用并执行线程的 run 方法。一个线程只能启动一次。
- 如果自定义线程类继承 Thread 类,直接启动。
public class Main { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread("ThreadName"); t1.start(); t2.start(); } }Copy to clipboardErrorCopied 复制代码
- 如果自定义线程类实现 Runnable 接口,则需要借助 Thread 类启动线程。
public class Main { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t1 = new Thread(mythread); // 由系统指定默认线程名 Thread-X Thread t2 = new Thread(mythread, "ThreadName"); // 开发者自定义线程名 t1.start(); t2.start(); } }Copy to clipboardErrorCopied 复制代码
同时定义和启动线程
通过匿名内部类方式,我们可以实现同时定义和启动线程的简洁写法。
public class Main { public static void main(String[] args) { Thread thread = new Thread(new Runnable(){ public void run(){ System.out.println(Thread.currentThread().getName()); } }).start(); } } // Lambda 表达式简写一 public class Main { public static void main(String[] args) { Thread thread = new Thread(() -> { System.out.println(Thread.currentThread().getName()); }).start(); } } // Lambda 表达式简写二 public class Main { public static void main(String[] args) { Test test = new test(); Thread t = new Thread(test::method); t.start(); } } class Test { public void method() { System.out.println(Thread.currentThread().getName()); } }Copy to clipboardErrorCopied 复制代码
线程弹出执行
Thread 类定义了 yield 方法。当前线程执行到 Thread.yield()
方法,会停止运行进入就绪状态。但线程切换到就绪状态后,什么时候被 JVM 调度回运行状态开发者无法控制。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t1 = new Thread(mythread); Thread t2 = new Thread(mythread); t1.start(); t2.start(); } static class MyThread implements Runnable { @Override public void run() { int count = 0; for (int i = 0; i < 10000; i++) { Thread.yield(); // 切换到就绪状态 count++; System.out.println(count); } } } }Copy to clipboardErrorCopied 复制代码
线程暂停执行
Thread 类定义了 sleep 方法。当前线程执行到 Thread.sleep(1000)
方法,会停止运行进入阻塞状态,但仍会保持对象锁,其他线程不可访问其资源。直到超时后进入就绪状态。调用 sleep 方法需要捕获或抛出 InterruptException 异常。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t1 = new Thread(mythread); Thread t2 = new Thread(mythread); t1.start(); t2.start(); } static class MyThread implements Runnable { @Override public void run() { int count = 0; for (int i = 0; i < 10000; i++) { try{ Thread.sleep(1000); // 当前线程暂停 1s } catch(InterruptException e){ e.printStackTrace(); } count++; System.out.println(count); } } } }Copy to clipboardErrorCopied 复制代码
线程交互
Object 类定义了 wait 和 notify 方法,通常被用于线程间交互/通信。必须在同步环境下(synchronized)使用,否则会抛出 IllegalMonitorStateException 异常。假定 obj 为同步环境上锁的对象:
线程等待
当前线程执行 obj.wait()
方法,线程会停止运行并释放对象锁 obj,其他线程可以访问其资源。同时线程进入 obj 对象的等待池,直到被 notify 方法唤醒进入就绪状态。调用 wait 方法需要捕获或抛出 InterruptException 异常。
wait 方法允许计时等待。当前线程执行 obj.wait(1000)
方法,计时结束后线程会被自动唤醒进入就绪状态。
线程唤醒
当前线程执行 obj.notify()
方法,会随机从 obj 对象等待池中选择一个线程唤醒,使其进入就绪状态。但是 notify 方法不会释放当前进程的对象锁,如果该线程持有 obj 对象的锁,当前线程释放锁后被唤醒的其他线程才能被执行。如果想被唤醒线程先执行,notify 方法后添加 wait 方法释放锁。
当前线程执行 obj.notifyall()
方法,会将所有 obj 对象等待池中所有线程唤醒。
public class ThreadDemo { public static void main(String[] args) { MyThread t = new MyThread("t"); synchronized(t) { // 对 t 设置对象锁 try { t.start(); System.out.println("1"); t.wait(); // 当前线程释放 t 锁,进入 t 对象等待池 System.out.println("4"); } catch (InterruptedException e) { e.printStackTrace(); } } } static class MyThread extends Thread { @Override public void run() { synchronized (this) { // 对 t 设置对象锁 Thread.sleep(1000); System.out.println("2"); this.notify(); // 随机唤醒一个 t 对象等待池中的线程 System.out.println("3"); } } } }Copy to clipboardErrorCopied 复制代码
其他线程优先
Thread 类定义了 join 方法,其底层通过调用 wait 方法实现。当前线程执行 t.join()
方法,线程会停止运行并释放对象锁,同时线程进入线程 t 对象的等待池,直到被唤醒进入就绪状态。通常用于主线程 main,等到线程 t 终止时自动被唤醒。调用 join 方法需要捕获或抛出 InterruptException 异常。
同样允许计时等待。当前线程执行 t.join(1000)
方法,计时结束后线程会被自动唤醒进入就绪状态,无须等待子线程结束时唤醒。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t = new Thread(mythread); t.start(); System.out.println("1"); t.join(); // 子线程结束后才被唤醒 System.out.println("3"); } static class MyThread implements Runnable { @Override public void run() { Thread.sleep(1000); System.out.println("2"); } } }Copy to clipboardErrorCopied 复制代码
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t = new Thread(mythread); t.start(); System.out.println("1"); t.join(500); // 0.5s 后自动被唤醒 System.out.println("2"); } static class MyThread implements Runnable { @Override public void run() { Thread.sleep(1000); System.out.println("3"); } } }Copy to clipboardErrorCopied 复制代码
如果我们手动给线程 t 加锁,即使计时结束后线程被唤醒进入就绪状态,但仍无法立刻拿到锁进入运行状态。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t = new Thread(mythread); t.start(); System.out.println("1"); t.join(500); // 自动被唤醒后仍要等待线程 t 释放锁 System.out.println("3"); } static class MyThread implements Runnable { @Override public void run() { synchronized (currentThread()) { try { Thread.sleep(1000); } catch (InterruptedException e) {} System.out.println("2"); } } } }Copy to clipboardErrorCopied 复制代码
线程中断
调用 t.stop()
方法可以强制终止线程 t 运行,但强制中断线程可能会造成意想不到的问题,已不推荐使用。
目前主要采用设置线程中断标志的方式,向线程发送中止信号。由线程自行终止运行:
- 执行
t.interrupt()
方法,将线程 t 中断标志设为 true 。 - 执行
t.isInterrupted()
方法,查看线程 t 中断标志。 - 执行
t.interrupted()
方法,查看线程 t 中断标志然后将其设为 false 。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t = new Thread(mythread); t.start(); try { Thread.sleep(500); } catch (InterruptedException e) {} t.interrupt(); // 设置中断标志为 true } static class MyThread implements Runnable { @Override public void run() { while (true) { System.out.print("hello"); if(this.isInterrupted()){ // 查看中断标志,若为 true 结束循环 break; } } } } }Copy to clipboardErrorCopied 复制代码
捕获异常
调用 t.interrupt()
方法时如果线程 t 处在阻塞/等待状态,会立即退出阻塞/等待状态,并抛出 InterruptedException 异常。因此在调用 sleep/wait/join 方法时需要捕获 InterruptedException 异常。
开发者经常用 t.interrupt()
方法使线程退出阻塞/等待状态,同时也可以在捕获异常时使线程立即终止。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t = new Thread(mythread); t.start(); System.out.println("1"); t.interrupt(); // 设置中断标志,使线程 t 退出等待状态 try { Thread.sleep(500); } catch (InterruptedException e) {} System.out.println("3"); } static class MyThread implements Runnable { @Override public void run() { try { Thread.sleep(100000); } catch (InterruptedException e) { // 被设置中断标志后会自动抛出异常 System.out.println("2"); return; // 捕获异常后终止线程 } } } }