## 一、概述
世间万物都可以同时完成很多工作。例如,人体可以同时进行呼吸、血液循环、思考问题等活动。用户既可以使用计算机听歌,也可以编写文档和发送邮件,而这些活动的完成可以同时进行。这种同时执行多个操作的“思想”在 Java 中被称为并发,而将并发完成的每一件事称为线程。
在 Java 中,并发机制非常重要,但并不是所有程序语言都支持线程。在以往的程序中,多以一个任务完成以后再进行下一个任务的模式进行,这样下一个任务的开始必须等待前一个任务的结束。Java 语言提供了并发机制,允许开发人员在程序中执行多个线程,每个线程完成一个功能,并与其他线程并发执行。这种机制被称为多线程。
> Windows 系统是多任务操作系统,它以进程为单位。一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序。
> 系统可以分配给每个进程一段有限的执行 CPU 的时间(也称为 CPU 时间片),CPU 在这段时间中执行某个进程,然后下一个时间段又跳到另一个进程中去执行。由于 CPU 切换的速度非常快,给使用者的感受就是这些任务似乎在同时运行,所以使用多线程技术后,可以在同一时间内运行更多不同种类的任务。
线程可以理解成是在进程中独立运行的子任务。比如,QQ.exe 运行时就有很多的子任务在同时运行。像好友视频、下载文件、传输数据、发送表情等,这些不同的任务或者说功能都可以同时运行,其中每一项任务完全可以理解成是“线程”在工作,传文件、听音乐、发送图片表情等功能都有对应的线程在后台默默地运行。
## 二、线程的创建
### 2.1 通过 Thread 类创建线程
```
public class MyThread extends Thread {
public void run() {
System.out.println(getName() + " 线程正在运行");
}
}
```
当一个类继承 Thread 类后,就可以在该类中覆盖 run() 方法,将实现线程功能的代码写入 run() 方法中,然后调用 Thread 类的 start() 方法启动线程。
```
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主线程开始运行");
Thread thread = new MyThread();
thread.start();
// thread.run();
System.out.println("主线程运行结束");
}
}
```
> 如果 start() 方法调用一个已经启动的线程,系统将会抛出 IllegalThreadStateException 异常。
```
主线程开始运行
主线程运行结束
Thread-0 线程正在运行
```
MyThread 类中的 start() 方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的 run() 方法。这个过程其实就是让系统安排一个时间来调用 Thread 中的 run() 方法,也就是使线程得到运行,启动线程,具有异步执行的效果。
```
主线程开始运行
Thread-0 线程正在运行
主线程运行结束
```
如果调用代码 thread.run() 就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由 main 主线程来调用 run() 方法,也就是必须等 run() 方法中的代码执行完后才可以执行后面的代码。
```
public class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " 线程运行第" + i + "次");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主线程开始运行");
MyThread mt = new MyThread("myThread");
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程运行第" + i + "次");
}
System.out.println("主线程运行结束");
}
}
```
从上面的运行结果来看,MyThread 类中 run() 方法执行的时间要比主线程晚。这也说明在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。同时也验证了线程是一个子任务,CPU 以不确定的方式,或者说以随机的时间来调用线程中的 run() 方法。
```
public class MyThread extends Thread {
private int i;
public MyThread3(int i) {
super();
this.i = i;
}
public void run() {
System.out.println("当前数字:" + i);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyThread m = new MyThread(i);
m.start();
}
}
}
```
从运行结果中可以看到,虽然调用时数字是有序的,但是由于线程执行的随机性,导致输出的数字是无序的,而且每次顺序都不一样。除了异步调用之外,同步执行线程 start() 方法的顺序不代表线程启动的顺序。
*****
【选择】下列选项中,哪两句的说法是错误的()(选择两项)
```
A 线程是比进程还要小的运行单位
B Thread 类位于 java.thread 包下
C run() 方法用于启动线程
D CPU 使用时间片轮转的工作方法,可以让多个程序轮流占用 CPU,达到同时运行的效果
```
【阅读】以下代码中,在(1)处加入哪条语句能成功启动线程()
```
public class ThreadOne extends Thread {
public void run() {
System.out.println("执行 ThreadOne 进程");
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadOne one = new ThreadOne();
// (1)
}
}
```
【选择】通过 Thread 类创建线程时要()(选择三项)
```
A 继承 Thread 类
B 在子类中重写 run() 方法
C 实现 Runnable 接口
D 调用 start() 方法启动线程
```
【编程】通过继承 Thread 类的方式创建线程,并在线程体中通过循环打印输出如演示所示的内容。
```
打印机正在打印1
打印机正在打印2
打印机正在打印3
打印机正在打印4
打印机正在打印5
打印机正在打印6
打印机正在打印7
打印机正在打印8
打印机正在打印9
打印机正在打印10
```
### 2.2 实现 Runnable 接口创建线程
如果要创建的线程类已经有一个父类,这时就不能再继承 Thread 类,因为 Java 不支持多继承,所以可以通过实现 Runnable 接口来应对这样的情况。
> 从 JDK 的 API 中可以发现,实质上 Thread 类实现了 Runnable 接口,其中的 run() 方法正是对 Runnable 接口中 run() 方法的具体实现。
实现 Runnable 接口的程序会创建一个 Thread 对象,并将 Runnable 对象与 Thread 对象相关联。Thread 类有如下两个与 Runnable 有关的构造方法:
```
1. public Thread(Runnable r)
2. public Thread(Runnable r, String name)
```
使用 Runnable 接口启动线程的基本步骤如下:
```
1. 创建一个 Runnable 对象。
2. 使用参数带 Runnable 对象的构造方法创建 Thread 实例。
3. 调用 start() 方法启动线程。
```
:-: ![](http://cndpic.dodoke.com/388b7c911f81a97a6a99cf510e1a08a7)
【例题】案例演示如何实现 Runnable 接口,以及如何启动线程。
```
public class MyRunnable implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + "运行中!");
}
public static void main(String[] args) {
Runnable rn = new MyRunnable();
Thread th1 = new Thread(rn);
th1.start();
Thread th2 = new Thread(rn);
th2.start();
}
}
```
```
public class MyRunnable implements Runnable {
int i = 0; // 此时多个线程共用一个成员变量
public void run() {
while(i < 10) {
System.out.println(Thread.currentThread().getName() + "运行第" + (i++) + "次");
}
}
public static void main(String[] args) {
Runnable rn = new MyRunnable();
Thread th1 = new Thread(rn);
th1.start();
Thread th2 = new Thread(rn);
th2.start();
}
}
```
*****
【选择】用 Runnable 接口创建线程的主要工作如下,它们正确的先后顺序为()(选择一项)
```
1) 通过实现类的对象创建线程类的对象
2) 声明实现 Runnable 接口的类
3) 调用 start() 方法启动线程
4) 创建实现类的对象
5) 在实现类内实现 run() 方法
```
```
A 1-4-2-5-3
B 2-1-4-5-3
C 2-5-4-1-3
D 1-5-2-4-3
```
【编程】编写代码完成以下任务:
1. 通过实现 Runnable 接口的方式创建线程类 Cat 和 Dog,run() 方法实现的功能为:加入一个循环长度为3的循环,分别循环输出信息`A cat`和`A dog`。
2. 在测试类中分别创建 Cat 和 Dog 类的对象,启动两个线程。
3. 在测试类中创建一个循环长度为3的for循环,打印输出信息`main thread`。
```
main thread
main thread
main thread
Thread-1A dog
Thread-1A dog
Thread-1A dog
Thread-0A cat
Thread-0A cat
Thread-0A cat
```
## 三、线程的生命周期
### 3.1 线程的状态和生命周期
线程具有生命周期,主要包括 7 种状态,分别是出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。
1. 出生状态:用户在创建线程时所处的状态,在用户使用该线程实例调用`start()`方法之前,线程都处于出生状态。
2. 就绪状态:也称可执行状态,当用户调用`start()`方法之后,线程处于就绪状态。
3. 运行状态:当线程得到系统资源后进入运行状态。
4. 等待状态:当处于运行状态下的线程调用 Thread 类的`wait()`方法时,该线程就会进入等待状态。进入等待状态的线程必须调用 Thread 类的`notify()`方法才能被唤醒。`notifyAll()`方法是将所有处于等待状态下的线程唤醒。
5. 休眠状态:当线程调用 Thread 类中的`sleep()`方法时,则会进入休眠状态。
6. 阻塞状态:如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入/输出结束时,线程进入就绪状态。对阻塞的线程来说,即使系统资源关闭,线程依然不能回到运行状态。
7. 死亡状态:当线程的`run()`方法执行完毕,线程进入死亡状态。
> 提示:一旦线程进入可执行状态,它会在就绪状态与运行状态下辗转,同时也可能进入等待状态、休眠状态、阻塞状态或死亡状态。
*****
【选择】关于线程的状态和生命周期的说法,正确的是()(选择两项)
```
A 只有获取到 CPU 的使用权,线程才能从可运行状态转为运行状态
B 调用 start() 方法可以使线程处于可运行状态
C 如果正在运行的线程异常终止,则线程会处于阻塞状态
D 一个正在运行的线程,调用 join() 方法,则会处于终止状态
```
### 3.2 sleep 方法的使用
```
public static void sleep(long millis) { ... }
```
* 指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
* 这个正在执行的线程是指 Thread.currentThread() 返回的线程。
* 参数为休眠的时间,单位是毫秒。
```
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "运行第" + i + "次");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SleepTest {
public static void main(String[] args) {
Runnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
```
*****
【选择】以下说法错误的是()(选择一项)
```
A sleep() 方法的参数是以毫秒为单位的
B 使用实现 Runnable 接口的方式创建线程,一定要重写 run 方法
C 当创建线程对象,线程即进入创建状态
D 调用 sleep 方法时,不需要处理异常
```
【阅读】关于下列代码,说法正确的是()
```
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("正在运行" + i);
Thread.sleep(500);
}
}
}
public class Test {
public static void main(String[] args) {
Runnable m = new MyRunnable();
Thread th = new Thread(m);
th.start();
}
}
```
【编程】利用线程输出`a~z`的26个字母(横向输出),要求每隔一秒钟输出一个字母。
```
abcdefghijklmnopqrstuvwxyz
```
### 3.3 join 方法的使用
```
1. public final void join() 等待调用该方法的线程结束后才能执行。
2. public final void join(long millis) 等待该线程终止的最长时间为 millis 毫秒。
```
```
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "正在执行第" + i + "次");
}
}
}
public class JoinTest {
public static void main(String[] args) {
Runnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
try {
t1.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
```
*****
【选择】下列关于 Thread 类中的 join() 方法说法错误的是()(选择两项)
```
A 调用 join() 方法可以使其他线程由正在运行状态变成阻塞状态
B join() 方法可以通过 Thread 类名直接访问
C 子类中可以重写 join() 方法
D join() 方法的作用是等待调用该方法的线程结束后才能执行
```
### 3.4 线程的优先级
> Java 为线程类提供了10个优先级,优先级可以用整数1~10表示,超过范围会抛出异常。主线程优先级的默认值为 5。
* 优先级常量
```
MAX_PRIORITY:线程的最高优先级 10
MIN_PRIORITY:线程的最低优先级 1
NORM_PRIORITY:线程的默认优先级 5
```
* 使用 Thread 类中的 setPriority() 方法来设置线程的优先级。语法如下:
```
public final void setPriority(int newPriority);
```
* 使用 Thread 类中的 getPriority() 方法来获取线程的优先级。语法如下:
```
public final int getPriority();
```
```
public class MyRunnable implements Runnable {
public void run() {
System.out.println("优先级为" + Thread.currentThread().getPriority());
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行" + i + "次");
}
}
public static void main(String[] args) {
int mainPriority = Thread.currentThread().getPriority(); // 获取主线程的优先级
System.out.println("主线程的优先级为" + mainPriority);
Runnable r = new MyRunnable();
Thread t1 = new Thread(r, "线程1");
Thread t2 = new Thread(r, "线程2");
t1.setPriority(10);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
```
*****
【选择】下列说法正确的是()(选择一项)
```
A 设置优先级的方法为 public int setPriority(int n)
B 优先级可用1-10的整数表示
C 获取优先级的方法是 public void getPriorty()
D 在 Java 中,优先级高的线程一定会比优先级低的线程先运行
```
## 四、线程同步
* 各个线程是通过竞争 CPU 时间而获得运行机会的
* 各个线程什么时候得到 CPU 时间,占用多久,是不可预测的
* 一个正在运行着的线程在什么地方被暂停是不确定的
```
public class Bank {
private int balance;
public Bank(int balance) {
this.balance = balance;
}
// getter setter ...
@Override
public String toString() {
return "Bank{balance=" + balance + "}";
}
// 存款
public void saveBalance(int value) {
// 获取当前的账号余额
int balance = getBalance();
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
setBalance(balance + value);
// 输出存款后的账户余额
System.out.println("存款后的账户余额为" + getBalance());
}
// 取款
public void drawBalance(int value) {
int balance = getBalance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(balance - value);
System.out.println("取款后的账户余额为" + getBalance());
}
}
public class SaveBalance implements Runnable {
Bank bank;
public SaveBalance(Bank bank) {
this.bank = bank;
}
public void run() {
bank.saveBalance(100);
}
}
public class DrawBalance implements Runnable {
Bank bank;
public DrawBalance(Bank bank) {
this.bank = bank;
}
public void run() {
bank.drawBalance(200);
}
}
public class BankTest {
public static void main(String[] args) {
Bank bank = new Bank(1000);
Runnable sb = new SaveBalance(bank);
Runnable db = new DrawBalance(bank);
Thread save = new Thread(sb);
Thread draw = new Thread(db);
save.start();
draw.start();
try {
save.join();
draw.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(bank);
}
}
```
为了处理共享资源竞争,可以使用同步机制。所谓同步机制,指的是两个线程同时作用在一个对象上,应该保持对象数据的统一性和整体性。[](http://c.biancheng.net/java/)Java 提供 synchronized 关键字,为防止资源冲突提供了内置支持。
* 在一个类中,用 synchronized 关键字声明的方法为同步方法。格式如下:
~~~
public [static] synchronized 类型名称 方法名称() { ... }
~~~
* synchronized 也可以作用于代码块。对于同步块,synchronized 获取的是参数中的对象锁。格式如下:
```
synchronized(obj) { ... }
```
Java 有一个专门负责管理线程对象中同步方法访问的工具——同步模型监视器,它的原理是为每个具有同步代码的对象准备唯一的一把“锁”。当多个线程访问对象时,只有取得锁的线程才能进入同步方法,其他访问共享对象的线程停留在对象中等待。
```
public synchronized void saveBalance(int value) { ... }
public void drawBalance(int value) {
synchronized(this) { ... }
}
```
*****
【选择】运行下列代码,结果是()(选择两项)
```
// Barber(理发师) 类
public class Barber {
public void wash() {
synchronized(this) {
System.out.println("打湿头发");
System.out.println("洗头水");
System.out.println("冲洗");
}
}
public synchronized void cut() {
System.out.println("剪短");
System.out.println("烫发");
}
}
public class Cut extends Thread {
Barber b;
public Cut(Barber b) {
this.b = b;
}
public void run() {
b.cut();
}
}
public class Wash extends Thread {
Barber b;
public Wash(Barber b) {
this.b = b;
}
public void run() {
b.wash();
}
}
public class Test {
public static void main(String[] args) {
Barber b = new Barber();
Cut cut = new Cut(b);
Wash wash = new Wash(b);
cut.start();
wash.start();
}
}
```
```
A 剪短 打湿头发 烫头 洗头水 冲洗
B 剪短 烫发 打湿头发 洗头水 冲洗
C 打湿头发 洗头水 冲洗 剪短 烫发
D 剪短 打湿头发 洗头水 冲洗 烫发
```
【选择】下列对关键字 synchronized 说法不正确的是()(选择一项)
```
A synchronized(同步),即协调不同线程之间的工作
B synchronized 关键字可以用在成员方法中
C 保证多个线程可以同时执行和结束
D 保证共享对象在同一时刻只能被一个线程访问
```
## 五、线程间通信
```
wait(): 中断方法的执行,使线程等待
notify(): 唤醒处于等待的某一个线程,使其结束等待
notifyAll(): 唤醒所有处于等待的线程,使他们结束等待
```
```
public class Count {
private int n;
boolean flag = false;
public synchronized int getN() throws InterruptedException{
if (!flag) wait();
System.out.println("消费:" + n);
flag = false;
notifyAll();
return n;
}
public synchronized void setN(int n) throws InterruptedException {
if (flag) wait();
System.out.println("生产:" + n);
this.n = n;
flag = true;
notifyAll();
}
}
public class Producer implements Runnable {
private Count count;
public Producer(Count count) {
this.count = count;
}
public void run() {
int i = 0;
while (true) {
try {
count.setN(i++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer implements Runnable {
private Count count;
public Consumer(Count count) {
this.count = count;
}
public void run() {
while (true) {
try {
count.getN();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class CountTest {
public static void main(String[] args) {
Count count = new Count();
new Thread(new Producer(count)).start();
new Thread(new Consumer(count)).start();
}
}
```
*****
【选择】以下说法错误的是()(选择一项)
```
A wait() 方法用于使线程等待
B notify() 方法用于唤醒一个线程
C notifyAll() 方法用于唤醒多个线程
D 使用 wait() 方法阻塞的线程,可以不用唤醒
```
- 阶段一 Java 零基础入门
- 步骤1:基础语法
- 第01课 初识
- 第02课 常量与变量
- 第03课 运算符
- 第04课 选择结构
- 第05课 循环结构
- 第06课 一维数组
- 第08课 方法
- 第09课 数组移位与统计
- 第10课 基础语法测试
- 第09课 基础语法测试(含答案)
- 步骤2:面向对象
- 第01课 类和对象
- 第02课 封装
- 第03课 学生信息管理
- 第04课 继承
- 第05课 单例模式
- 第06课 多态
- 第07课 抽象类
- 第08课 接口
- 第09课 内部类
- 第10课 面向对象测试
- 第10课 面向对象测试(含答案)
- 步骤3:常用工具类
- 第01课 异常
- 第02课 包装类
- 第03课 字符串
- 第04课 集合
- 第05课 集合排序
- 第06课 泛型
- 第07课 多线程
- 第08课 输入输出流
- 第09课 案例:播放器
- 第10课 常用工具测试(一)
- 第10课 常用工具测试(一)(答案)
- 第10课 常用工具测试(二)
- 第10课 常用工具测试(二)(答案)
- 阶段二 从网页搭建入门 JavaWeb
- 步骤1:HTML 与 CSS
- 第01课 HTML 入门
- 第01课 HTML 入门(作业)
- 第02课 CSS 入门
- 第02课 CSS 入门(作业)
- 第03课 CSS 布局
- 第03课 CSS 布局(作业)
- 步骤2:JavaScript 与前端案例
- 第01课 JavaScript 入门
- 第01课 JavaScript 入门(作业)
- 第02课 仿计算器
- 第03课 前端油画商城案例
- 第04课 轮播图
- 第05课 网页搭建测试
- 第05课 网页搭建测试(含答案)
- 步骤3:JavaScript 教程
- 入门
- 概述
- 基本语法
- 数据类型
- 概述
- 数值
- 字符串
- undefined, null 和布尔值
- 对象
- 函数
- 数组
- 运算符
- 算术运算符
- 比较运算符
- 布尔运算符
- 位运算符
- 运算顺序
- 语法专题
- 数据类型的转换
- 错误处理机制
- 标准库
- String
- Date
- Math
- DOM
- 概述
- Document 节点
- 事件
- EventTarget 接口
- 事件模型
- 常见事件
- 阶段三 数据库开发与实战
- 步骤1:初始数据库操作
- 第01课 数据类型
- 第02课 表的管理
- 第03课 数据管理
- 第04课 常用函数
- 第05课 JDBC 入门
- 第06课 Java 反射
- 第07课 油画商城
- 第08课 数据库基础测试
- 步骤2:MyBatis 从入门到进阶
- 第01课 IntelliJ IDEA 开发工具入门
- 第02课 Maven 入门
- 第03课 工厂模式
- 第04课 MyBatis 入门
- 第05课 MyBatis 进阶
- 第06课 商品信息管理
- 第07课 MyBatis 基础测试
- 步骤3:Redis 数据库与 Linux 下项目部署
- 第01课 Linux 基础
- 第02课 Linux 下 JDK 环境搭建及项目部署
- 第03课 Redis 入门
- 阶段四 SSM 到 Spring Boot 入门与综合实战
- 步骤1:Spring 从入门到进阶
- 第01课 Spring 入门
- 第02课 Spring Bean 管理
- 第03课 Spring AOP
- 第04课 基于 AspectJ 的 AOP 开发
- 第05课 JDBC Template
- 第06课 Spring 事务管理
- 第07课 人员管理系统开发
- 第08课 Spring 从入门到进阶测试
- 步骤2:Spring MVC 入门与 SSM 整合开发
- 第01课 Spring MVC 入门与数据绑定
- 第02课 Restful 风格的应用
- 第03课 SpringMVC 拦截器
- 第04课 办公系统核心模块
- 步骤3:Spring Boot 实战
- 第01课 Spring Boot 入门
- 第02课 校园商铺项目准备
- 第03课 校园商铺店铺管理
- 第04课 校园商铺商品管理及前台展示
- 第05课 校园商铺框架大换血
- 步骤4:Java 面试
- 第01课 面试准备
- 第02课 基础面试技巧
- 第03课 Web基础与数据处理
- 第04课 主流框架