多线程

  • 线程
    • 操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
    • 简单理解: 应用软件中互相独立, 可以同时运行的功能

并发和并行

  • 并发: 在同一时刻, 有多个指令在单个CPU上交替执行

  • 并行: 在同一时刻, 有多个指令在多个CPU上同时执行

实现方式

  • 继承Thread类的方式进行实现

    • 定义一个类继承Thread
    • 重写run方法
    • 创建子类的对象, 并启动线程
  • 实现Runnable接口的方式进行实现

    • 定义一个类实现Runnable接口
    • 重写run方法
    • 创建自己的类的对象
    • 创建一个Tread类的对象, 并开启线程
  • 利用Callable接口和FutureTask对象的方式实现

    • 特点: 可以获取到多线程运行的结果
    • 创建一个类MyCallble实现Callable接口
    • 重写call (有返回值, 表示多线程运行的结果)
    • 创建MyCallble对象 (表示多线程要执行的任务)
    • 创建FutureTask对象 (管理多线程运行的结果)
    • 创建Thread类的对象, 并启动

常用成员方法

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
/*
* 返回此线程的名称
*/
String getName();

/*
* 设置线程的名字(也可以用构造方法设置)
* 如果没有给线程设置名字, 线程也是有默认的名字的, 格式: Thread-X (X序号, 从0开始)
*/
void setName(String name)

/*
* 获取当前线程的对象
*/
static Thread currentThread()

/*
* 让线程休眠指定的时间, 单位为ms
* 哪条线程执行到这个方法, 那么哪条线程就会在这停留对应的时间
*/
static void sleep(long time)

/*
* 设置线程的优先级
* 最小为1, 最大为10, 默认为5
*/
setPriority(int newPriority)

/*
* 获取线程的优先级
*/
final int getPriority()

/*
* 设置为守护线程(备胎线程)
* 当其他的非守护线程执行完毕之后, 守护线程会陆续结束
*/
final void setDaemon(boolean on)

/*
* 出让线程/礼让线程
*/
public static void yield()

/*
* 插入线程/插队线程
* 将方法调用者的线程插入到当前线程之前
*/
public final void join()

安全问题

同步代码块

  • 把操作共享数据的代码锁起来

  • 格式

    1
    2
    3
    4
    5
    // 锁对象是任意的, 但一定要唯一, static修饰
    // 如: static Object obj = new Object();
    synchronized (锁对象) {
    // 操作共享数据的代码;
    }
  • 特点

    • 锁默认打开, 有一个线程进去了, 锁自动关闭
    • 里面的代码全部执行完毕, 线程出来, 锁自动打开

同步方法

  • synchronized关键字加到方法上

  • 格式: 修饰符 synchronized 返回值类型 方法名(方法参数) {...}

  • 特点

    • 同步方法是锁住方法里面所有的代码
    • 锁对象不能自己指定
      • 非静态: this
      • 静态: 当前类的字节码文件对象

Lock锁

  • Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Lock是接口不能直接实例化, 这里采用它的实现类ReentrantLock来实例化
static Lock lock = new ReentrantLock();

public void run() {
while(true) {
lock.lock(); // 上锁
try {
// 操作共享数据的代码
} catch() {
// 异常捕获
} finally {
lock.unlock(); // 解锁
}
}
}

生产者和消费者

  • 又称等待唤醒机制, 是一个经典的多线程协作模式

  • 以厨师与食客为例

    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 {
    @Override
    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 {
    @Override
    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();
    }
    }

阻塞队列实现

image-20250118234452832

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
public class Cook extends Thread {

ArrayBlockingQueue<String> queue;

public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}

@Override
public void run() {
while (true) {
// 这里不用写同步代码块, 因为put的底层源码已经写了, take同理
try {
queue.put("面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}

public class Foodie extends Thread {

ArrayBlockingQueue<String> queue;

public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}

@Override
public void run() {
while (true) {
try {
String food = queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}

public class Main {
public static void main(String[] args) {
ArrayBlockingQueue queue = new ArrayBlockingQueue();

Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);

cook.setName("厨师");
foodie.setName("食客");

cook.start();
foodie.start();
}
}

线程的六种状态

image-20250118234503069

线程池

核心原理

  • 创建一个池子, 池子中是空的

  • 提供任务时, 池子会创建新的线程对象, 任务执行完毕, 线程归还给池子; 下次再提交任务时, 不需要创建新的线程, 直接复用已有的线程即可

  • 如果提交任务时, 池子中没有空闲线程, 也无法创建新的线程, 任务就会排队等待

代码实现

Executors: 线程池的工具类, 通过调用方法返回不同类型的线程池对象

  • 创建线程池

    方法名称 说明
    public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
    public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
  • 提交任务

  • 所有的任务全部执行完毕, 关闭线程池

1
2
3
4
5
6
7
8
9
10
11
// 1.获取线程池对象
ExecutorService pool = Executor.newFixedThreadPool(3);
// 2.提交任务
pool.sumit(new MyRunnable());
pool.sumit(new MyRunnable());
pool.sumit(new MyRunnable());
pool.sumit(new MyRunnable());
pool.sumit(new MyRunnable());
......
// 3.销毁线程池(通常不需要)
// pool.shutdown();

自定义线程池

  • 拒绝策略

    任务拒绝策略 说明
    ThreadPoolExecutor.AbortPolicy 默认策略: 丢弃任务并抛出RejectedExecutionException异常
    ThreadPoolExecutor.DiscardPolicy 丢弃任务, 但是不抛出异常, 不推荐的做法
    ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务, 然后把当前任务加入队列中
    ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法绕过线程池直接执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 参数一: 核心线程数量			   不能小于0
// 参数二: 最大线程数 不能小于等于0, 最大数量>=核心线程数量
// 参数三: 空闲线程最大存活时间 不能小于0
// 参数四: 时间单位 用TimeUnit指定
// 参数五: 任务队列, 即阻塞队列 不能为null
// 参数六: 创建线程工厂 不能为null
// 参数七: 任务的拒绝策略 不能为null
// ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量, 最大线程数量, 空闲线程最大存活时间, 时间单位, 任务队列, 创建线程工厂, 任务的拒绝策略);

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,
6,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);