(1-6-3)Java 多线程
目录
0.知识拓扑
1. 多线程相关概念
1.1 进程
1.2 线程
1.3 java 中的进程 与 线程概述
1.4 CPU、进程 与 线程的关系
2.多线程的创建方式
2.1 继承Thread类
2.2 实现Runnable接口
2.3 实现Callable接口
2.4 三种创建方式对比
3.线程同步
3.1 线程同步机制概述
3.2 线程安全的产生以及解决方式
4.线程池
4.1 概述
(1)JUC
(2)JUC支持的线程池种类
(3)ThreadPool线程池特点
4.2 线程池创建的4中方式
(1)创建定长线程池
(2)创建缓存线程池
(3)创建单线程线程池
(4)可调度线程池
5.小结
0.知识拓扑
- 了解多线程序的概念
- 创建多线程的三种方式
- 初识线程同步
- 线程安全问题与解决方式
- 初始线程池及其应用
1. 多线程相关概念
1.1 进程
1.2 线程
1.3 java 中的进程 与 线程概述
进程 | 线程 | |
概述 | 进程是操作系统资源分配的基本单位 | 线程是CPU调度的基本单位,是进程内的执行单元 |
资源占用 | 拥有独立的内存空间(堆、栈、方法区等),创建和销毁开销较大 | 共享进程的内存资源,创建和切换开销小 |
通信方式 | 进程间通信(IPC)较复杂(如管道、套接字、共享内存等) | 通信更简单(共享内存) |
实现方式 | Runtime.exec() 或ProcessBuilder 创建新的进程 |
|
线程安全 | 由于线程共享内存,需要考虑同步问题:
| |
线程池 | Java提供了
|
1.4 CPU、进程 与 线程的关系
2.多线程的创建方式
2.1 继承Thread类
/*
需求: 通过继承Thread的方式 来创建三个线程,
--> 模拟参赛者董老、 参赛者yang菜鸡的10秒短跑程序
*/
code
public class ExtendsThreadDemo {/*需求: 通过继承Thread的方式 来创建三个线程,--> 模拟参赛者董老、 参赛者yang菜鸡的10秒短跑程序*/class CreateThreadRunnner extends Thread {public int speed = 2;public CreateThreadRunnner(int speed){this.speed = speed;}@Overridepublic void run() {int x = 0;for (int i = 0; i < 10; i++) {Integer speed = new Random().nextInt(this.speed);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}x = x +i * speed;System.out.println("第"+ i + "s:" + this.getName() + "跑了" +x + "m【" + speed + "m/s】");}}}public void start() {CreateThreadRunnner createThreadRunnnerA = new CreateThreadRunnner(666);createThreadRunnnerA.setName("懂佬");CreateThreadRunnner createThreadRunnerB = new CreateThreadRunnner(9);createThreadRunnerB.setName("yang菜鸡");createThreadRunnnerA.start();createThreadRunnerB.start();}public static void main(String[] args) {new ExtendsThreadDemo().start();/*** 第0s:yang菜鸡跑了0m【6m/s】* 第0s:懂佬跑了0m【549m/s】* 第1s:懂佬跑了1m【1m/s】* 第1s:yang菜鸡跑了5m【5m/s】* 第2s:懂佬跑了623m【311m/s】* 第2s:yang菜鸡跑了13m【4m/s】* 第3s:懂佬跑了1121m【166m/s】* 第3s:yang菜鸡跑了13m【0m/s】* 第4s:懂佬跑了1565m【111m/s】* 第4s:yang菜鸡跑了25m【3m/s】* 第5s:yang菜鸡跑了50m【5m/s】* 第5s:懂佬跑了3290m【345m/s】* 第6s:yang菜鸡跑了74m【4m/s】* 第6s:懂佬跑了6200m【485m/s】* 第7s:懂佬跑了10267m【581m/s】* 第7s:yang菜鸡跑了130m【8m/s】* 第8s:yang菜鸡跑了170m【5m/s】* 第8s:懂佬跑了10819m【69m/s】* 第9s:yang菜鸡跑了206m【4m/s】* 第9s:懂佬跑了13195m【264m/s】*/}
}
2.2 实现Runnable接口
/*需求: 通过实现Runnable接口的方式 来创建三个线程,--> 模拟参赛者索尼克、夏特、小红的5秒短跑程序*/
public class ImplementsRunnableDemo {/*需求: 通过实现Runnable接口的方式 来创建三个线程,--> 模拟参赛者索尼克、夏特、小红的5秒短跑程序*/class CreateRunner implements Runnable{public int speed;public CreateRunner(int speed) {this.speed = speed;}@Overridepublic void run() {int sum = 0;for(int k = 0; k < 5; k++){Integer speed = new Random().nextInt(this.speed);try{Thread.sleep(500);}catch(InterruptedException e){e.printStackTrace();}sum += k * speed;System.out.println("第"+ k + "s:" + Thread.currentThread().getName() + "跑了" +sum + "m【" + speed + "m/s】");}}}public void start() {CreateRunner runner1 = new CreateRunner(999);Thread thread1 = new Thread(runner1);thread1.setName("刺猬索尼克");thread1.start();Thread thread2 = new Thread(new CreateRunner(888));thread2.setName("夏特");thread2.start();Thread thread3 = new Thread(new CreateRunner(788));thread3.setName("小红");thread3.start();}public static void main(String[] args) {ImplementsRunnableDemo demo = new ImplementsRunnableDemo();demo.start();/*** C:\Users\iayan\.jdks\corretto-1.8.0_432\bin\java.exe "-* 第0s:小红跑了0m【287m/s】* 第0s:夏特跑了0m【805m/s】* 第0s:刺猬索尼克跑了0m【21m/s】* 第1s:夏特跑了130m【130m/s】* 第1s:小红跑了445m【445m/s】* 第1s:刺猬索尼克跑了876m【876m/s】* 第2s:夏特跑了944m【407m/s】* 第2s:刺猬索尼克跑了2554m【839m/s】* 第2s:小红跑了1519m【537m/s】* 第3s:刺猬索尼克跑了3067m【171m/s】* 第3s:小红跑了3325m【602m/s】* 第3s:夏特跑了1976m【344m/s】* 第4s:刺猬索尼克跑了6235m【792m/s】* 第4s:小红跑了5177m【463m/s】* 第4s:夏特跑了5328m【838m/s】*/}
}
2.3 实现Callable接口
允许线程创建后将值返回
public class ImplementsCallableDemo {/*需求: 通过实现Runnable接口的方式 来创建三个线程,--> 模拟参赛者索尼克、夏特、小红的5秒短跑程序*/class CreateCallable implements Callable<Integer> {public int speed;public String name;public CreateCallable(int speed) {this.speed = speed;}@Overridepublic Integer call() throws Exception {Integer speed = new Random().nextInt(this.speed);Integer sumLength = 0;for (int j = 1; j <= 6; j++) {Thread.sleep(2000);sumLength += j * speed;System.out.println();System.out.println("第"+ j + "s:" + this.name + "跑了" +sumLength + "m【" + speed + "m/s】");}return sumLength;}}public void start() throws ExecutionException, InterruptedException {// 创建线程池ExecutorService executorService = Executors.newFixedThreadPool(3);CreateCallable thread1 = new CreateCallable(95);thread1.name = "索尼克";CreateCallable thread2 = new CreateCallable(97);thread2.name = "夏特";CreateCallable thread3 = new CreateCallable(92);thread3.name = "大红";Future<Integer> r1 = executorService.submit(thread1);Future<Integer> r2 = executorService.submit(thread2);Future<Integer> r3 = executorService.submit(thread3);// 关闭线程池executorService.shutdown();System.out.println(thread1.name + "累计跑了:" + r1.get() + "m");System.out.println(thread2.name + "累计跑了:" + r2.get() + "m");System.out.println(thread3.name + "累计跑了:" + r3.get() + "m");}public static void main(String[] args) throws ExecutionException, InterruptedException {new ImplementsCallableDemo().start();/***第1s:索尼克跑了46m【46m/s】第1s:大红跑了79m【79m/s】第1s:夏特跑了71m【71m/s】第2s:大红跑了237m【79m/s】第2s:夏特跑了213m【71m/s】第2s:索尼克跑了138m【46m/s】第3s:索尼克跑了276m【46m/s】第3s:大红跑了474m【79m/s】第3s:夏特跑了426m【71m/s】第4s:夏特跑了710m【71m/s】第4s:索尼克跑了460m【46m/s】第4s:大红跑了790m【79m/s】第5s:大红跑了1185m【79m/s】第5s:夏特跑了1065m【71m/s】第5s:索尼克跑了690m【46m/s】第6s:大红跑了1659m【79m/s】第6s:索尼克跑了966m【46m/s】第6s:夏特跑了1491m【71m/s】索尼克累计跑了:966m夏特累计跑了:1491m大红累计跑了:1659m*/}
}
2.4 三种创建方式对比
3.线程同步
3.1 线程同步机制概述
线程同步的三种方式:
/*** 线程同步的三种方式* 1. synchronized代码块, 需要指定一个自定义对象作为所对象* 2. synchronized方法, 通过this对象决定哪个对象拥有执行的权力* 3. synchronized静态方法, Printer.class 进行加锁*/
/*** 需求:* 创建5个打印线程,* 有序的打印“不可逆转* 不可逆转* 不可逆转* 不可逆转* 不可逆转”*/
code:
public class SyncLockDemo {/*** 需求:* 创建5个打印线程,* 有序的打印“不可逆转* 不可逆转* 不可逆转* 不可逆转* 不可逆转”*//*** 线程同步的三种方式* 1. synchronized代码块, 需要指定一个自定义对象作为所对象* 2. synchronized方法, 通过this对象决定哪个对象拥有执行的权力* 3. synchronized静态方法, Printer.class 进行加锁*/class Printer{Object printLock = new Object();/****/public void print(){synchronized(printLock){try {Thread.sleep(300);System.out.print("不");Thread.sleep(300);System.out.print("可");Thread.sleep(300);System.out.print("逆");Thread.sleep(300);System.out.print("转");System.out.println();} catch (InterruptedException e) {e.printStackTrace();}}}// 02 synchronized方法public synchronized void print2(){try {Thread.sleep(300);System.out.print("不");Thread.sleep(300);System.out.print("可");Thread.sleep(300);System.out.print("逆");Thread.sleep(300);System.out.print("转");System.out.println();} catch (InterruptedException e) {e.printStackTrace();}}}class PrintTask implements Runnable{public Printer printer;@Overridepublic void run() {
// printer.print();printer.print2();}}// 00- 单线程实现
// public void startPrint(){
// Printer printer = new Printer();
// printer.print();
// }// 01 -多线程实现public void startPrint(){Printer printer = new Printer();for(int k = 0; k < 5; k++){PrintTask printTask = new PrintTask();printTask.printer = printer;new Thread(printTask).start();}}public static void main(String[] args) {SyncLockDemo demo = new SyncLockDemo();demo.startPrint();/** 0.未加同步锁** 不不不不不可可可可可逆逆逆逆逆转* 转* 转* 转* 转*//** 1.加上同步锁* 不可逆转* 不可逆转* 不可逆转* 不可逆转* 不可逆转*/}
}
3.2 线程安全的产生以及解决方式
线程安全解决超卖问题:
(1)商品库存类
// 商品库存类
public class Stock {// 当前商品库存剩余量 --》 3个public static int count = 3;}
(2)消费者类
// 商品库存类
public class Stock {// 当前商品库存剩余量 --》 3个public static int count = 3;}
(3)模拟商城消费商品
// 模拟商城消费商品
public class Mall {public synchronized void sale(){if(Stock.count > 0){try{// 模拟商城办理销售业务 用时3秒Thread.sleep(3);}catch (InterruptedException e){e.printStackTrace();}// 销售成功 库存剑少Stock.count --;System.out.println("商品销售成功");}else{System.out.println("卖完了");}}public static void main(String[] args) {// 实例化商城唯一对象Mall mall = new Mall();// 模拟3名顾客 同时进如商城消费商品for(int m = 0; m < 6; m++){Consumer consumer = new Consumer();consumer.mall = mall;Thread thread = new Thread(consumer);thread.start();}try{// 模拟下班后 查看库存Thread.sleep(1000);System.out.println("当前商品的库存为:" + Stock.count);}catch(InterruptedException e){e.printStackTrace();}/*** 商品销售成功* 商品销售成功* 商品销售成功* 卖完了* 卖完了* 卖完了* 当前商品的库存为:0*/}
}
4.线程池
为什么不选用实现Runnable接口,而要使用线程池?
- Runnable需要新建线程,性能较差
- 线程缺乏统一管理
- 可能会无限制的新建线程
- 线程间会相互竞争,严重的情况下会占用过多的系统资源导致死机或者内存溢出
4.1 概述
(1)JUC
并发工具包
(2)JUC支持的线程池种类
在java.util.concurrent中,提供了工具类Executors(调度器) 对象来创建线程池
可创建的线程池有四种:
- 定长线程池
- 可缓存线程池
- 单线程池
- 调度线程池
(3)ThreadPool线程池特点
- 重用存在的线程,减少线程对象创建、消亡的开销
- 线程总数可控,提高资源的利用率。
- 提供额外功能,定时执行、定期执行、监控等
4.2 线程池创建的4中方式
(1)创建定长线程池
/*** 01 创建定长线程池* * 特点:固定线程总数, 空闲线程用于执行任务。 若线程都在工作,则后续的任务处于等待状态* * 需求: 创建一个容量为8的线程池,并发打印1~88*/
code实现
/*** 01 创建定长线程池** 特点:固定线程总数, 空闲线程用于执行任务。 若线程都在工作,则后续的任务处于等待状态** 需求: 创建一个容量为8的线程池,并发打印1~88*/
public class FixedPool {public static void main(String[] args) {ExecutorService threadPool = Executors.newFixedThreadPool(8);for(int p = 1; p <= 88; p++){final int p_index = p;// 通过匿名内部类 快速创建threadPool.execute(new Runnable(){@Overridepublic void run(){System.out.println(Thread.currentThread().getName() + ":" + p_index);}});}/*** pool-1-thread-1:1* pool-1-thread-6:6* pool-1-thread-2:2* pool-1-thread-6:10* pool-1-thread-6:12* pool-1-thread-6:13* pool-1-thread-5:5* pool-1-thread-5:15* pool-1-thread-5:16* ......*/threadPool.shutdown();}
}
(2)创建缓存线程池
/*** 02 创建可缓存线程池** 特点是: 容量无限大, 若线程池中没有可用的线程则进行创建, 有的话则调度空闲的线程** 需求: 创建一个容量为8的线程池,并发打印1~77*/
/*** 02 创建可缓存线程池** 特点是: 容量无限大, 若线程池中没有可用的线程则进行创建, 有的话则调度空闲的线程** 需求: 创建一个容量为8的线程池,并发打印1~77*/
public class CachedPool {public static void main(String[] args) {// ExecutorService 用于管理线程池ExecutorService threadPool = Executors.newCachedThreadPool(); //创建一个可缓存线程池for (int p = 0; p <= 77; p++) {final int p_index = p;threadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ":" + p_index);}});}/*** pool-1-thread-2:1* pool-1-thread-6:5* pool-1-thread-7:6* pool-1-thread-4:3* pool-1-thread-8:7* pool-1-thread-3:2* pool-1-thread-5:4* pool-1-thread-9:8* pool-1-thread-9:12* pool-1-thread-3:14* pool-1-thread-10:9*/threadPool.shutdown();}
}
(3)创建单线程线程池
// 03 应用单线程线程池
public class SinglePool {public static void main(String[] args) {// 创建单线程线程池ExecutorService threadPool = Executors.newSingleThreadExecutor();// 打印 80 到 66for(int p = 80; p >= 66; p--){final int finalP = p;threadPool.execute(new Runnable(){@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ":" + "finalP:" + finalP);}});}threadPool.shutdown();/*** pool-1-thread-1:finalP:80* pool-1-thread-1:finalP:79* pool-1-thread-1:finalP:78* pool-1-thread-1:finalP:77* pool-1-thread-1:finalP:76* pool-1-thread-1:finalP:75* pool-1-thread-1:finalP:74* pool-1-thread-1:finalP:73* pool-1-thread-1:finalP:72* pool-1-thread-1:finalP:71* pool-1-thread-1:finalP:70* pool-1-thread-1:finalP:69* pool-1-thread-1:finalP:68* pool-1-thread-1:finalP:67* pool-1-thread-1:finalP:66*/}
}
(4)可调度线程池
// 04 应用可调度线程池
public class ScheduleThreadPool {public static void main(String[] args) {// 调度线程池ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(7);// 延时3秒执行, 每6秒执行一次scheduledPool.scheduleAtFixedRate(new Runnable(){@Overridepublic void run(){System.out.println(new Date() + "延时3秒执行, 每6秒执行一次");}},3,6, TimeUnit.SECONDS);/*** Wed Jun 04 18:40:18 CST 2025延时3秒执行, 每6秒执行一次* Wed Jun 04 18:40:24 CST 2025延时3秒执行, 每6秒执行一次* Wed Jun 04 18:40:30 CST 2025延时3秒执行, 每6秒执行一次* Wed Jun 04 18:40:36 CST 2025延时3秒执行, 每6秒执行一次* Wed Jun 04 18:40:42 CST 2025延时3秒执行, 每6秒执行一次* Wed Jun 04 18:40:48 CST 2025延时3秒执行, 每6秒执行一次* Wed Jun 04 18:40:54 CST 2025延时3秒执行, 每6秒执行一次*/}
}