[TOC]
一个 CUP 在任意时刻只能运行一个线程,我们说的多任务其实是切割时间块来达到该目的。


在使用 Thread 类时,我们都是可以通过派生 Thread 类覆写 run(),或者创建 Thread 实例时,传入一个 Runnable 实例,当然在 Runnable 实例中也要覆写 run()
需要注意的是,直接调用 Thread 实例的 run() 是无效的。直接调用 run() 相当于调用了一个普通的 Java 方法,并不会启动新线程。
必须调用 Thread 实例的 start() 方法才能启动新线程。如果查看 Thread 类的源代码,会看到 start() 内部调用了一个 private native void start0() 方法, native 修饰符表示这个方法是有 JVM 虚拟机内部的 C 代码实现的。

线程的状态

  • New:新创建的线程,尚未执行;
  • Runnable:运行中的线程,正在执行 run() 方法的Java代码;
  • Blocked:运行中的线程,因为某些操作被阻塞而挂起;
  • Waiting:运行中的线程,因为某些操作在等待中;
  • Timed Waiting:运行中的线程,因为执行 sleep() 方法正在计时等待
  • Terminated:线程已终止,因为 run() 方法执行完毕。 ```
    1. ┌─────────────┐
    2. New
    3. └─────────────┘
    ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌─────────────┐ ┌─────────────┐ ││ Runnable │ │ Blocked ││ └─────────────┘ └─────────────┘ │┌─────────────┐ ┌─────────────┐│ │ Waiting │ │Timed Waiting│ │└─────────────┘ └─────────────┘│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
    1. ┌─────────────┐
    2. Terminated
    3. └─────────────┘
  1. 线程启动后就可以在 Runnable, Blocked, Waiting, Timed Waiting 这四个状态之间切换,直到最后变成 Terminated 状态。
  2. #### 线程终止的原因:
  3. - 正常终止:`run()` 遇到 `return` 语句正常结束
  4. - 意外终止:`run()` 因为未捕获的异常导致线程终止
  5. - 强制终止:调用 `Thread` 实例的 `stop()` 方法强制终止(由于历史原因,此方法已经不推荐使用)
  6. 一个线程还可以等待另一个线程直到其运行结束。`Thread` 实例中的 `join()`,可以保证 `Thread` 实例线程结束之后在继续运行:

Thread thread = new Thread(() -> { System.out.println(“hello”); }); System.out.println(“start”); thread.start(); thread.join(); // it will throw InterruptedException System.out.println(“end”);

// always output: start -> hello -> end

  1. 另外,可以指定 `join(long)` 来设置一个等待时间,超过等待时间就不在等待,如果是 `join(0)` 就是一直等待到该实例线程结束。
  2. ---
  3. 每个线程都有各自的局部变量,互不影响。
  4. ---
  5. 线程安全:如果一个类被设计为允许多线程正确访问,那么这个类就是线程安全。<br />比如:这里有一个计数器类:

public class Counter { private int count = 0;

  1. public void add(int n) {
  2. synchronized(this) { // 锁住 this
  3. count += n;
  4. }
  5. }
  6. public void dec(int n) {
  7. synchronized(this) {
  8. count -= n;
  9. }
  10. }
  11. public int get() {
  12. return count;
  13. }

}

  1. 因为 `synchronized` 锁住的是 `this`,即当前实例。这使得创建多个 `Counter` 实例的时候互不影响,可以并发执行。<br />所以 `Counter` 就是线程安全的,同样 `java.lang.StringBuffer` 也是线程安全的。<br />还有一些**不变类**,如 `String`, `Integer`, `LocalDate` 它们的所有变量都是 `final` ,只能读不能写。所以不变类也是线程安全的。<br />最后,类似 `Math` 这些只提供静态方法,没有成员变量的类,也是线程安全的。
  2. > 没有特殊说明时,一个类默认是非线程安全的。
  3. 大部分类,例如 `ArrayList`,都是非线程安全的类,我们不能在多线程中修改他们,否则就会出现数据异常。但是,如果所有线程只是读取,不写入。那么 `ArrayList` 可以安全的在线程中共享。<br />上述 `Counter` 类中,锁住 `this` 实例,实际上可以使用 `synchronized` 修饰这个方法:

public void add(int n) { synchronized(this) { // 锁住 this count += n; } } // 解锁

// like public synchronized void add(int n) { // 锁住 this count += n; } // 解锁

  1. 需要注意的是,对一个静态方法添加 `synchronized` 修饰符,他会锁住该类的 `Class` 实例:

public synchronized static void test(int n) { … }

// like public static void test(int n) { synchronized(Counter.class) { … } }

```


这种加锁和解锁之间的代码块我们称之为临界区(Critical Section),任何时候临界区最多只有一个线程能执行。