JavaSE进阶——Day09 多线程(二)

该篇主要讲解Java中线程通讯、线程池

线程通讯

概述

在多线程程序中,某个线程进入到“等待状态”时,必须由其他线程来唤醒“等待状态”的线程

API方法

等待方法

  • 特殊之处:会释放掉对象锁
1
2
wait() //无限等待(只能由其他线程唤醒)
wait(long 毫秒) //计时等待(时间到了自动唤醒)

唤醒方法

  • 特殊之处:不会释放掉对象锁
1
2
notify() //唤醒同一个对象锁上处于“等待状态”的任意一个线程
notifyAll() //唤醒同一个对象锁上处于“等待状态”的所有线程

wait()方法和notify()方法,都必须绑定在对象锁上。

1
2
3
4
5
Object lock = new Object(); //对象锁

lock.wait();

lock.notify();

案例——生产者消费者

共享资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package security.demo2;

/**
* @Author EthanLiu 16693226842@163.com
* @Date 2024/4/28 11:39
* @Project JavaCode_SE_Advance
* @Theme 生产者与消费者----资源
*/
//共享资源
public class Resource {
public static int num = 0;
public static final String LOCK = "对象锁";
}

生产者:

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
package security.demo2;

/**
* @Author EthanLiu 16693226842@163.com
* @Date 2024/4/28 11:41
* @Project JavaCode_SE_Advance
* @Theme 生产者
*/
public class ProductTask implements Runnable {
@Override
public void run() {
while (true) {
//加入锁对象, 同步代码块
synchronized (Resource.LOCK) {
//没吃的: 生产
if (Resource.num == 0) {
System.out.println(Thread.currentThread().getName() + "没有生产,开始生产......");
//生产
Resource.num = 1;
System.out.println(Thread.currentThread().getName() + "生产了" + Resource.num + "个");
//唤醒
Resource.LOCK.notify();
//有吃的: 等待
} else {
System.out.println(Thread.currentThread().getName() + "已生产食物,等待顾客食用......");
try {
Resource.LOCK.wait();//无限等待, 会释放掉对象锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}

消费者:

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
package security.demo2;

/**
* @Author EthanLiu 16693226842@163.com
* @Date 2024/4/28 11:42
* @Project JavaCode_SE_Advance
* @Theme 消费者
*/
public class CustomerTask implements Runnable {
@Override
public void run() {
while (true) {
//同步代码块
synchronized (Resource.LOCK) {
//没吃的: 等待
if (Resource.num == 0) {
//等待
try {
System.out.println(Thread.currentThread().getName() + "没有发现食物, 等待中......");
Resource.LOCK.wait(); //无限等待,会释放掉对象锁
} catch (InterruptedException e) {
e.printStackTrace();
}
//有吃的: 消费
} else {
System.out.println(Thread.currentThread().getName() + "发现食物, 开始消费了");
Resource.num--;
System.out.println(Thread.currentThread().getName() + "消费完了");
//唤醒: 生产者可以继续生产食物了
Resource.LOCK.notify();
}
}
}
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package security.demo2;

/**
* @Author EthanLiu 16693226842@163.com
* @Date 2024/4/28 11:58
* @Project JavaCode_SE_Advance
* @Theme 生产者与消费者---测试
*/
public class Test {
public static void main(String[] args) {
new Thread(new ProductTask(), "生产者 ").start();
new Thread(new CustomerTask(), "消费者 ").start();
}
}

线程池

线程使用存在的问题

  • 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
    如果大量线程在执行,会涉及到线程间上下文的切换,会极大的消耗CPU运算资源

线程池的介绍

  • 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

线程池使用的大致流程

  • 创建线程池指定线程开启的数量
  • 提交任务给线程池,线程池中的线程就会获取任务,进行处理任务。
  • 线程处理完任务,不会销毁,而是返回到线程池中,等待下一个任务执行。
  • 如果线程池中的所有线程都被占用,提交的任务,只能等待线程池中的线程处理完当前任

线程池的好处

  • 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  • 提高响应速度。当任务到达时,任务可以不需要等待线程创建 , 就能立即执行。
  • 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存 (每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java提供好的线程池

  • java.util.concurrent.ExecutorService 是线程池接口类型。使用时我们不需自己实现,JDK已经帮我们实现好了
  • 获取线程池我们使用工具类java.util.concurrent.Executors的静态方
    • public static ExecutorService newFixedThreadPool (int num) : 指定线程池最大线程池数量获取线程池
  • 线程池ExecutorService的相关方法
    • Future submit(Callable task)
    • Future<?> submit(Runnable task)
  • 关闭线程池方法(一般不使用关闭方法,除非后期不用或者很长时间都不用,就可以关闭)
    • void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务

线程池处理Runnable任务

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
package com.itheima.threadpool_demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
1 需求 :
使用线程池模拟游泳教练教学生游泳。
游泳馆(线程池)内有3名教练(线程)
游泳馆招收了5名学员学习游泳(任务)。

2 实现步骤:
创建线程池指定3个线程
定义学员类实现Runnable,
创建学员对象给线程池
*/
public class Test1 {
public static void main(String[] args) {
// 创建指定线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);

// 提交任务
threadPool.submit(new Student("小花"));
threadPool.submit(new Student("小红"));
threadPool.submit(new Student("小明"));
threadPool.submit(new Student("小亮"));
threadPool.submit(new Student("小白"));

threadPool.shutdown();// 关闭线程池
}
}

class Student implements Runnable {
private String name;

public Student(String name) {
this.name = name;
}

@Override
public void run() {
String coach = Thread.currentThread().getName();
System.out.println(coach + "正在教" + name + "游泳...");

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(coach + "教" + name + "游泳完毕.");
}
}

线程池处理Callable任务

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
package com.itheima.threadpool_demo;

import java.util.concurrent.*;

/*
需求: Callable任务处理使用步骤
1 创建线程池
2 定义Callable任务
3 创建Callable任务,提交任务给线程池
4 获取执行结果

<T> Future<T> submit(Callable<T> task) : 提交Callable任务方法    
返回值类型Future的作用就是为了获取任务执行的结果。
Future是一个接口,里面存在一个get方法用来获取值

练一练:使用线程池计算 从0~n的和,并将结果返回
*/
public class Test2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建指定线程数量的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);

Future<Integer> future = threadPool.submit(new CalculateTask(100));
Integer sum = future.get();
System.out.println(sum);
}
}

// 使用线程池计算 从0~n的和,并将结果返回
class CalculateTask implements Callable<Integer> {
private int num;

public CalculateTask(int num) {
this.num = num;
}

@Override
public Integer call() throws Exception {
int sum = 0;// 求和变量
for (int i = 0; i <= num; i++) {
sum += i;
}
return sum;
}
}