10.多线程
多线程
- 线程
- 操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
- 简单理解: 应用软件中互相独立, 可以同时运行的功能
并发和并行
-
并发: 在同一时刻, 有多个指令在单个CPU上交替执行
-
并行: 在同一时刻, 有多个指令在多个CPU上同时执行
实现方式
-
继承Thread类的方式进行实现
- 定义一个类继承Thread
- 重写run方法
- 创建子类的对象, 并启动线程
-
实现Runnable接口的方式进行实现
- 定义一个类实现Runnable接口
- 重写run方法
- 创建自己的类的对象
- 创建一个Tread类的对象, 并开启线程
-
利用Callable接口和FutureTask对象的方式实现
- 特点: 可以获取到多线程运行的结果
- 创建一个类MyCallble实现Callable接口
- 重写call (有返回值, 表示多线程运行的结果)
- 创建MyCallble对象 (表示多线程要执行的任务)
- 创建FutureTask对象 (管理多线程运行的结果)
- 创建Thread类的对象, 并启动
常用成员方法
1 | /* |
安全问题
同步代码块
-
把操作共享数据的代码锁起来
-
格式
1
2
3
4
5// 锁对象是任意的, 但一定要唯一, static修饰
// 如: static Object obj = new Object();
synchronized (锁对象) {
// 操作共享数据的代码;
} -
特点
- 锁默认打开, 有一个线程进去了, 锁自动关闭
- 里面的代码全部执行完毕, 线程出来, 锁自动打开
同步方法
-
把
synchronized
关键字加到方法上 -
格式:
修饰符 synchronized 返回值类型 方法名(方法参数) {...}
-
特点
- 同步方法是锁住方法里面所有的代码
- 锁对象不能自己指定
- 非静态:
this
- 静态: 当前类的字节码文件对象
- 非静态:
Lock锁
-
Lock实现提供比使用
synchronized
方法和语句可以获得更广泛的锁定操作
1 | // Lock是接口不能直接实例化, 这里采用它的实现类ReentrantLock来实例化 |
生产者和消费者
-
又称等待唤醒机制, 是一个经典的多线程协作模式
-
以厨师与食客为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76// 以桌子作为联系两个线程的中介
public class Dest {
public static int foodFlag = 0;
public static int count = 10;
public static final Object lock = new Object();
}
public class Cook extends Thread {
public void run() {
/*
* 写安全线程的套路:
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到了末尾(到了末尾)
* 4.判断共享数据是否到了末尾(没到,执行核心逻辑)
*/
while (true) {
synchronized (Dest.lock) {
if (Dest.count == 0) {
break;
} else {
if (Dest.foodFlag == 1) {
try {
Dest.lock.wait(); // 将该线程与锁对象绑定在一起
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
System.out.println("厨师做了一碗面条");
Dest.foodFlag = 1;
Dest.lock.notifyAll();
}
}
}
}
}
}
public class Foodie extends Thread {
public void run() {
while (true) {
synchronized (Dest.lock) {
if (Dest.count == 0) {
break;
} else {
if (Dest.foodFlag == 0) {
try {
Dest.lock.wait(); // 将该线程与锁对象绑定在一起
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
System.out.println("食客吃了这碗面条,还能再吃" + --Dest.count + "碗");
Dest.lock.notifyAll();
Dest.foodFlag = 0;
}
}
}
}
}
}
public class Main {
public static void main(String[] args) {
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.setName("厨师");
foodie.setName("食客");
cook.start();
foodie.start();
}
}
阻塞队列实现
1 | public class Cook extends Thread { |
线程的六种状态
线程池
核心原理
-
创建一个池子, 池子中是空的
-
提供任务时, 池子会创建新的线程对象, 任务执行完毕, 线程归还给池子; 下次再提交任务时, 不需要创建新的线程, 直接复用已有的线程即可
-
如果提交任务时, 池子中没有空闲线程, 也无法创建新的线程, 任务就会排队等待
代码实现
Executors: 线程池的工具类, 通过调用方法返回不同类型的线程池对象
-
创建线程池
方法名称 说明 public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池 public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池 -
提交任务
-
所有的任务全部执行完毕, 关闭线程池
1 | // 1.获取线程池对象 |
自定义线程池
-
拒绝策略
任务拒绝策略 说明 ThreadPoolExecutor.AbortPolicy 默认策略: 丢弃任务并抛出RejectedExecutionException异常 ThreadPoolExecutor.DiscardPolicy 丢弃任务, 但是不抛出异常, 不推荐的做法 ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务, 然后把当前任务加入队列中 ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法绕过线程池直接执行
1 | // 参数一: 核心线程数量 不能小于0 |