[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()
方法执行完毕。 ```
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌─────────────┐ ┌─────────────┐ ││ Runnable │ │ Blocked ││ └─────────────┘ └─────────────┘ │┌─────────────┐ ┌─────────────┐│ │ Waiting │ │Timed Waiting│ │└─────────────┘ └─────────────┘│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┌─────────────┐
│ New │
└─────────────┘
│
▼
│
▼
┌─────────────┐
│ Terminated │
└─────────────┘
线程启动后就可以在 Runnable, Blocked, Waiting, Timed Waiting 这四个状态之间切换,直到最后变成 Terminated 状态。
#### 线程终止的原因:
- 正常终止:`run()` 遇到 `return` 语句正常结束
- 意外终止:`run()` 因为未捕获的异常导致线程终止
- 强制终止:调用 `Thread` 实例的 `stop()` 方法强制终止(由于历史原因,此方法已经不推荐使用)
一个线程还可以等待另一个线程直到其运行结束。`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
另外,可以指定 `join(long)` 来设置一个等待时间,超过等待时间就不在等待,如果是 `join(0)` 就是一直等待到该实例线程结束。
---
每个线程都有各自的局部变量,互不影响。
---
线程安全:如果一个类被设计为允许多线程正确访问,那么这个类就是线程安全。<br />比如:这里有一个计数器类:
public class Counter { private int count = 0;
public void add(int n) {
synchronized(this) { // 锁住 this
count += n;
}
}
public void dec(int n) {
synchronized(this) {
count -= n;
}
}
public int get() {
return count;
}
}
因为 `synchronized` 锁住的是 `this`,即当前实例。这使得创建多个 `Counter` 实例的时候互不影响,可以并发执行。<br />所以 `Counter` 就是线程安全的,同样 `java.lang.StringBuffer` 也是线程安全的。<br />还有一些**不变类**,如 `String`, `Integer`, `LocalDate` 它们的所有变量都是 `final` ,只能读不能写。所以不变类也是线程安全的。<br />最后,类似 `Math` 这些只提供静态方法,没有成员变量的类,也是线程安全的。
> 没有特殊说明时,一个类默认是非线程安全的。
大部分类,例如 `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; } // 解锁
需要注意的是,对一个静态方法添加 `synchronized` 修饰符,他会锁住该类的 `Class` 实例:
public synchronized static void test(int n) { … }
// like public static void test(int n) { synchronized(Counter.class) { … } }
```
这种加锁和解锁之间的代码块我们称之为临界区(Critical Section),任何时候临界区最多只有一个线程能执行。