java的多线程
文章目录
- 创建线程
- 什么是线程?
- 什么是多线程?
- 如何在程序中创建出多条线程?
- 方式一:继承Thread类
- 方式二:实现Runnable接口
- 方式三:实现Callable接口
- 三种创建方式的对比
- 线程的常用方法
- Thread提供的常用方法
- Thread提供的常见构造器
- 展示前五种方法的用法
- Sleep方法
- 线程插队的join方法
- 线程安全
- 认识线程安全
- 模拟线程安全问题
- 线程同步
- 认识线程同步
- 线程同步的常见方案
- 加锁的三种方式
- 方式一:同步代码块
- 方式二:同步方法
- 方式三:lock锁
- 线程池
- 认识线程池
- 创建线程池
- 方式一:通过ThreadPoolExecutor创建线程池
- 七大参数说明
- 处理Runnable任务
- 处理Callable任务
- 方式二通过Executors工具类创建线程池
- 并发、并行
- 抢红包 案例
创建线程
什么是线程?
线程(Thread)是一个程序内部的一条执行流程。
程序中如果只有一条执行流程,那这个程序就是单线程的程序。
public static void main(String[] args) {// 代码…for (int i = 0; i < 10; i++) {System.out.println(i);}// 代码...
}
什么是多线程?
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
多线程用在哪里,有什么好处
百度网盘的一边上传一边下载
再例如:消息通信、淘宝、京东系统都离不开多线程技术。
如何在程序中创建出多条线程?
- 方式一:继承Thread类
- 方式二:实现Runnable接口
- 方式三:实现Callable接口
方式一:继承Thread类
多线程的创建方式一:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
方式一优缺点:
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
创建线程的注意事项
- 启动线程必须是调用start方法,不是调用run方法。
- 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
- 只有调用start方法才是启动一个新的线程执行。
- 不要把主线程任务放在启动子线程之前。
- 这样主线程一直是先跑完的,相当于是一个单线程的效果了。
public class ThreadDemo1 {// main方法本身是由一条主线程负责推荐执行的。public static void main(String[] args) {// 目标:认识多线程,掌握创建线程的方式一:继承Thread类来实现// 4、创建线程类的对象:代表线程。Thread t1 = new MyThread();// 5、调用start方法,启动线程。还是调用run方法执行的t1.start(); // 启动线程,让线程执行run方法for (int i = 0; i < 5; i++) {System.out.println("主线程输出:" + i);}//主线程输出:0//子线程输出:0//主线程输出:1//子线程输出:1//主线程输出:2//子线程输出:2//子线程输出:3//子线程输出:4//主线程输出:3//主线程输出:4}
}// 1、定义一个子类继承Thread类,成为一个线程类。
class MyThread extends Thread {// 2、重写Thread类的run方法@Overridepublic void run() {// 3、在run方法中编写线程的任务代码(线程要干的活儿)for (int i = 0; i < 5; i++) {System.out.println("子线程输出:" + i);}}
}
方式二:实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理。
- 调用线程对象的start()方法启动线程
Thread类提供的构造器 | 说明 |
---|---|
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
方式二的优缺点
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
- 缺点:需要多一个Runnable对象。
public class ThreadDemo2 {public static void main(String[] args) {// 目标:掌握多线程的创建方式二:实现Runnable接口来创建。// 3、创建线程任务类的对象代表一个线程任务。Runnable r = new MyRunnable();// 4、把线程任务对象交给一个线程对象来处理Thread t1 = new Thread(r); // public Thread(Runnable r)// 5、启动线程t1.start();for (int i = 0; i < 5; i++) {System.out.println("主线程输出:" + i);}}
}// 1、定义一个线程任务类实现Runnable接口
class MyRunnable implements Runnable {// 2、重写run方法,设置线程任务@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("子线程输出:" + i);}}
}
匿名内部类写法
- 可以创建Runnable的匿名内部类对象。
- 再交给Thread线程对象。
- 再调用线程对象的start()启动线程。
public class ThreadDemo2_2 {public static void main(String[] args) {// 目标:掌握多线程的创建方式二:使用Runnable接口的匿名内部类来创建// 写法1Runnable r = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("子线程1输出:" + i);}}};Thread t1 = new Thread(r); // public Thread(Runnable r)t1.start();// 写法2new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("子线程2输出:" + i);}}}).start();// 写法3new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("子线程3输出:" + i);}}).start();// 主线程for (int i = 0; i < 5; i++) {System.out.println("主线程输出:" + i);}}
}
方式三:实现Callable接口
前两种线程创建方式都存在的一个问题。
假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。
如何解决这个问题
- JDK 5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)。
- 这种方式最大的优点:可以返回线程执行完毕后的结果。
利用Callable接口、FutureTask类创建接口
- 创建任务对象
- 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
- 把Callable类型的对象封装成FutureTask(线程任务对象)。
- 把线程任务对象交给Thread对象。
- 调用Thread对象的start方法启动线程。
- 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
FutureTask的API
FutureTask提供的构造器 | 说明 |
---|---|
public FutureTask<>(Callable call) | 把Callable对象封装成FutureTask对象。 |
FutureTask提供的方法 | 说明 |
---|---|
public V get() throws Exception | 获取线程执行call方法返回的结果。 |
线程创建方式三的优缺点
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
- 缺点:编码复杂一点。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;public class ThreadDemo3 {public static void main(String[] args) {// 目标:掌握多线程的创建方式三:实现Callable接口,方式三的优势:可以获取线程执行完毕后的结果的。// 3、创建一个Callable接口的实现类对象。Callable<String> c1 = new MyCallable(100);// 4、把Callable对象封装成一个真正的线程任务对象FutureTask对象。/*** 未来任务对象的作用?* a、本质是一个Runnable线程任务对象,可以交给Thread线程对象处理。* b、可以获取线程执行完毕后的结果。*/FutureTask<String> f1 = new FutureTask<>(c1); // public FutureTask(Callable<V> callable)// 5、把FutureTask对象作为参数传递给Thread线程对象。Thread t1 = new Thread(f1);// 6、启动线程。t1.start();Callable<String> c2 = new MyCallable(50);FutureTask<String> f2 = new FutureTask<>(c2); // public FutureTask(Callable<V> callable)Thread t2 = new Thread(f2);t2.start();// 获取线程执行完毕后返回的结果try {// 如果主线程发现第一个线程还没有执行完毕,会让出CPU,等第一个线程执行完毕后,才会往下执行!System.out.println(f1.get());// 子线程计算1-100的和是:5050} catch (Exception e) {e.printStackTrace();}try {// 如果主线程发现第二个线程还没有执行完毕,会让出CPU,等第一个线程执行完毕后,才会往下执行!System.out.println(f2.get());// 子线程计算1-50的和是:1275} catch (Exception e) {e.printStackTrace();}}
}// 1、定义一个实现类实现Callable接口
class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}// 2、实现call方法,定义线程执行体public String call() throws Exception {int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return "子线程计算1-" + n + "的和是:" + sum;}
}
三种创建方式的对比
对比说一下三种线程的创建方式,和不同点?
方式 | 优点 | 缺点 |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类,不能返回线程执行的结果 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。 | 编程相对复杂,不能返回线程执行的结果 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 | 编程相对复杂 |
线程的常用方法
Thread提供的常用方法
Thread提供的常用方法 | 说明 |
---|---|
public void run() | 线程的任务方法 |
public void start() | 启动线程 |
public String getName() | 获取当前线程的名称,线程名称默认是Thread-索引 |
public void setName(String name) | 为线程设置名称 |
public static Thread currentThread() | 获取当前执行的线程对象 |
public static void sleep(long time) | 让当前执行的线程休眠多少毫秒后,再继续执行 |
public final void join()… | 让调用当前这个方法的线程先执行完! |
Thread提供的常见构造器
Thread提供的常见构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target, String name) | 封装Runnable对象成为线程对象,并指定线程名称 |
展示前五种方法的用法
public class ThreadApiDemo1 {public static void main(String[] args) {// 目标:搞清楚线程的常用方法。Thread t1 = new MyThread("1号线程");// 起名字是为了区分不同线程。当然也可以使用他自带的名称。// t1.setName("1号线程");// 需要在线程启动之前设置名称t1.start();System.out.println(t1.getName()); // 线程默认名称是:Thread-索引Thread t2 = new MyThread("2号线程");// t2.setName("2号线程");t2.start();System.out.println(t2.getName()); // 线程默认名称是:Thread-索引// 哪个线程调用这个代码,这个代码就拿到哪个线程Thread m = Thread.currentThread(); // 主线程m.setName("主线程");System.out.println(m.getName()); // main}
}// 1、定义一个子类继承Thread类,成为一个线程类。
class MyThread extends Thread {public MyThread(String name) {super(name); // public Thread(String name) // 继承父类的构造方法}// 2、重写Thread类的run方法@Overridepublic void run() {// 3、在run方法中编写线程的任务代码(线程要干的活儿)for (int i = 0; i < 5; i++) {// 获取当前线程的名称System.out.println(Thread.currentThread().getName() +"子线程输出:" + i);}}
}
Sleep方法
public class ThreadApiDemo2 {public static void main(String[] args) {// 目标:搞清楚Thread类的Sleep方法(线程休眠)for (int i = 1; i <= 10; i++) {System.out.println(i);try {// 让当前执行的线程进入休眠状态,直到时间到了,才会继续执行。// 应用场景比如百度网盘充会员下载的快。Thread.sleep(1000); // 1000ms = 1s} catch (Exception e) {e.printStackTrace();}}}
}
线程插队的join方法
public class ThreadApiDemo3 {public static void main(String[] args) {// 目标:搞清楚线程的join方法:线程插队:让调用这个方法线程先执行完毕。MyThread2 t1 = new MyThread2();t1.start();for (int i = 1; i <= 5; i++) {System.out.println(Thread.currentThread().getName() +"线程输出:" + i);if(i == 1){try {t1.join(); // 插队 让t1线程先执行完毕,然后继续执行主线程} catch (Exception e) {e.printStackTrace();}}}// 结果如下//Thread-0子线程输出:1//main线程输出:1 // 这里只要是主线程的i==1时。先执行完子线程, 才会继续往下执行主线程。//Thread-0子线程输出:2//Thread-0子线程输出:3//Thread-0子线程输出:4//Thread-0子线程输出:5//main线程输出:2 // 主线程继续往下执行//main线程输出:3//main线程输出:4//main线程输出:5}
}class MyThread2 extends Thread {@Overridepublic void run() {for (int i = 1; i <= 5; i++) {System.out.println(Thread.currentThread().getName() +"子线程输出:" + i);}}
}
Thread类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会后续需要用到的时候再讲解。
线程安全
认识线程安全
什么是线程安全问题?
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
取钱的线程安全问题
场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?
线程安全问题出现的原因?
- 存在多个线程在同时执行
- 同时访问一个共享资源
- 存在修改该共享资源
模拟线程安全问题
需求
小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,用程序模拟两人同时取钱10万元。
分析
怎样描述小明和小红的共同账户呢?
设计一个账户类,创建一个账户对象,代表2人的共享账户
怎样模拟两人同时取钱?
设计一个线程类,创建并启动两个线程,在线程的run方法中调用账户的取钱方法
线程安全问题发生的原因是什么?
多个线程,同时访问同一个共享资源,且存在修改该资源。
// 取钱线程类
public class DrawThread extends Thread{private Account acc; // 记住线程对象要处理的账户对象。public DrawThread(String name, Account acc) {super(name);this.acc = acc;}@Overridepublic void run() {// 小明 小红 取钱acc.drawMoney(100000);}
}// 业务方法
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {private String cardId; // 卡号private double money; // 余额// 小明和小红都到这里来了取钱public void drawMoney(double money) {// 拿到当前谁来取钱。String name = Thread.currentThread().getName();// 判断余额是否足够if (this.money >= money) {// 余额足够,取钱System.out.println(name + "取钱成功,吐出了" + money + "元成功!");// 更新余额this.money -= money;System.out.println(name + "取钱成功,取钱后,余额剩余" + this.money + "元");} else {// 余额不足System.out.println(name + "取钱失败,余额不足");}}
}// 主方法
package com.itheima.demo3threadsafe;public class ThreadDemo1 {public static void main(String[] args) {// 目标:模拟线程安全问题。// 1、设计一个账户类:用于创建小明和小红的共同账户对象,存入10万。Account acc = new Account("ICBC-110", 100000);// 2、设计线程类:创建小明和小红两个线程,模拟小明和小红同时去同一个账户取款10万。new DrawThread("小明", acc).start();new DrawThread("小红", acc).start();// 小明取钱成功,吐出了100000.0元成功!// 小红取钱成功,吐出了100000.0元成功!// 小明取钱成功,取钱后,余额剩余0.0元// 小明取钱成功,取钱后,余额剩余-100000.0元}
}
线程同步
认识线程同步
线程同步是线程安全问题的解决方案。
线程同步的核心思想
让多个线程先后依次访问共享资源,这样就可以避免出现线程安全问题。
线程同步的常见方案
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
加锁的三种方式
- 同步代码块
- 同步方法
- lock锁
方式一:同步代码块
同步代码块
作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
synchronized(同步锁) { 访问共享资源的核心代码 }
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
同步锁的注意事项
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
// Account类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {private String cardId; // 卡号private double money; // 余额// 小明和小红都到这里来了取钱public void drawMoney(double money) {// 拿到当前谁来取钱。String name = Thread.currentThread().getName();// 判断余额是否足够synchronized ("dlei") {// 这里需要是一个唯一的,dlei字符串是个唯一的地址// 等这个人取完钱才会把这个唯一地址释放// 但是随便一个唯一值就行吗?// 下面会将if (this.money >= money) {// 余额足够,取钱System.out.println(name + "取钱成功,吐出了" + money + "元成功!");// 更新余额this.money -= money;System.out.println(name + "取钱成功,取钱后,余额剩余" + this.money + "元");} else {// 余额不足System.out.println(name + "取钱失败,余额不足");}}}
}// 取钱线程类
public class DrawThread extends Thread{private Account acc; // 记住线程对象要处理的账户对象。public DrawThread(String name, Account acc) {super(name);this.acc = acc;}@Overridepublic void run() {// 小明 小红 取钱acc.drawMoney(100000);}
}// main方法
public class ThreadDemo1 {public static void main(String[] args) {// 目标:线程同步的方式一演示:同步代码块// 1、设计一个账户类:用于创建小明和小红的共同账户对象,存入10万。Account acc = new Account("ICBC-110", 100000);// 2、设计线程类:创建小明和小红两个线程,模拟小明和小红同时去同一个账户取款10万。new DrawThread("小明", acc).start();new DrawThread("小红", acc).start();//小明取钱成功,吐出了100000.0元成功!//小明取钱成功,取钱后,余额剩余0.0元//小红取钱失败,余额不足}
}
锁对象随便选择一个唯一的对象好不好呢?
不好,会影响其他无关线程的执行。
锁对象的使用规范
- 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。
synchronized(this) { 访问共享资源的核心代码 }
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
synchronized(Account.class) { 访问共享资源的核心代码 }
// Account中的drawMoney方法public void drawMoney(double money) {// 拿到当前谁来取钱。String name = Thread.currentThread().getName();// 判断余额是否足够synchronized (this) {//1. 这里不能所有账户都用这一个锁,不然这个银行都要// 等其中一个账户取完钱另一个才能再去取钱。//2.使用this说明只有当前账号需要加锁。这一家人来取钱和另外一家人取// 钱互不影响。但是这一家人内部取钱(即同一个账号)多个人取钱会上锁。// .....省略}}// main方法
public class ThreadDemo1 {public static void main(String[] args) {// 1、设计一个账户类:用于创建小明和小红的共同账户对象,存入10万。Account acc = new Account("ICBC-110", 100000);new DrawThread("小明", acc).start();new DrawThread("小红", acc).start(); Account acc2 = new Account("ICBC-111", 80000);new DrawThread("小李", acc).start();new DrawThread("小王", acc).start(); }
}
总结
同步代码块是如何实现线程安全的?
- 对出现问题的核心代码使用synchronized进行加锁
- 每次只能一个线程占锁进入访问
同步代码块的同步锁对象有什么要求?
- 对于实例方法建议使用this作为锁对象。
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
方式二:同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
修饰符 synchronized 返回值类型 方法名称(形参列表) {操作共享资源的代码
}
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
同步方法底层原理
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
// 其他都一样
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {private String cardId; // 卡号private double money; // 余额// 小明和小红都到这里来了取钱public synchronized void drawMoney(double money) {// 拿到当前谁来取钱。String name = Thread.currentThread().getName();// 判断余额是否足够if (this.money >= money) {// 余额足够,取钱System.out.println(name + "取钱成功,吐出了" + money + "元成功!");// 更新余额this.money -= money;System.out.println(name + "取钱成功,取钱后,余额剩余" + this.money + "元");} else {// 余额不足System.out.println(name + "取钱失败,余额不足");}}
}
同步代码块好还是同步方法好?
+ 范围上:同步代码块锁的范围更小,同步方法锁的范围更大
+ 可读性:同步方法更好
同步方法是如何保证线程安全的?
- 对出现问题的核心方法使用synchronized修饰
- 每次只能一个线程占锁进入访问
同步方法的同步锁对象的原理?
- 对于实例方法默认使用this作为锁对象。
- 对于静态方法默认使用类名.class对象作为锁对象。
方式三:lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
构造器 | 说明 |
---|---|
public ReentrantLock() | 获得Lock锁的实现类对象 |
Lock的常用方法
方法名称 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
// 其他代码不变。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {private String cardId; // 卡号private double money; // 余额private final Lock lk = new ReentrantLock(); //lk使用final修饰防止被篡改 保护锁对象// 小明和小红都到这里来了取钱public void drawMoney(double money) {// 拿到当前谁来取钱。String name = Thread.currentThread().getName();lk.lock(); // 上锁try {// 判断余额是否足够if (this.money >= money) {// 余额足够,取钱System.out.println(name + "取钱成功,吐出了" + money + "元成功!");// 更新余额this.money -= money;System.out.println(name + "取钱成功,取钱后,余额剩余" + this.money + "元");} else {// 余额不足System.out.println(name + "取钱失败,余额不足");}} finally {// 放在这里解锁,如果try中报错,也会执行finally中的代码lk.unlock();// 解锁}}
}
锁对象建议加上什么修饰?
建议使用final修饰,防止被别人篡改
释放锁的操作建议放到哪里?
建议将释放锁的操作放到finally代码块中,确保锁用完了一定会被释放
线程池
认识线程池
什么是线程池?
线程池就是一个可以复用线程的技术。
不使用线程池的问题
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的, 创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
下面使用线程池来解决上述问题。
创建线程池
JDK 5.0起提供了代表线程池的接口:ExecutorService。
如何创建线程池对象?
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
方式一:通过ThreadPoolExecutor创建线程池
ThreadPoolExecutor类提供的构造器 | 作用 |
---|---|
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) | 使用指定的初始化参数创建一个新的线程池对象 |
七大参数说明
参数一:corePoolSize : 指定线程池的核心线程的数量。正式工: 3
参数二:maximumPoolSize:指定线程池的最大线程数量。最大员工数: 5, 临时工: 2
参数三:keepAliveTime :指定临时线程的存活时间。临时工空闲多久被开除
参数四:unit:指定临时线程存活(第三个参数)的时间单位(秒、分、时、天)
参数五:workQueue:指定线程池的任务队列。客人排队的地方
参数六:threadFactory:指定线程池的线程工厂。负责招聘员工的(hr)
参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)处理忙不过来咋办的问题(比如报错,删除某些线程)
谁代表线程池?线程池的创建方案有几种?
ExecutorService接口
ThreadPoolExecutor实现线程池对象的七个参数是什么意思?
使用线程池的实现类ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
处理Runnable任务
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行 Runnable 任务 |
Future<T> submit(Callable<T> task) | 执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果 |
void shutdown() | 等全部任务执行完毕后,再关闭线程池! |
List<Runnable> shutdownNow() | 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务 |
// 1、定义一个线程任务类实现Runnable接口
public class MyRunnable implements Runnable {// 2、重写run方法,设置线程任务@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "输出:" + i);}}
}// Main方法
import java.util.concurrent.*;public class ExecutorServiceDemo1 {public static void main(String[] args) {// 目标:创建线程池对象来使用。// 1、使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象。ExecutorService pool = new ThreadPoolExecutor(3, 5,10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());// 2、使用线程池处理任务!看会不会复用线程?Runnable target = new MyRunnable();pool.execute(target); // 提交第1个任务 创建第1个线程 自动启动线程处理这个任务pool.execute(target); // 提交第2个任务 创建第2个线程 自动启动线程处理这个任务pool.execute(target); // 提交第2个任务 创建第3个线程 自动启动线程处理这个任务pool.execute(target); // 复用线程pool.execute(target); // 复用线程//pool-1-thread-2输出:0 // 线程池1的线程1//pool-1-thread-1输出:0//pool-1-thread-3输出:0//pool-1-thread-1输出:1//pool-1-thread-2输出:1//pool-1-thread-1输出:2//pool-1-thread-3输出:1//pool-1-thread-1输出:3//pool-1-thread-2输出:2//pool-1-thread-1输出:4 //pool-1-thread-3输出:2//pool-1-thread-2输出:3//pool-1-thread-1输出:0//pool-1-thread-3输出:3//pool-1-thread-1输出:1//pool-1-thread-1输出:2//pool-1-thread-2输出:4//pool-1-thread-1输出:3//pool-1-thread-2输出:0//pool-1-thread-3输出:4//pool-1-thread-2输出:1//pool-1-thread-1输出:4//pool-1-thread-2输出:2//pool-1-thread-2输出:3//pool-1-thread-2输出:4// 一共五个4输出,所以线程池复用了2个线程,执行了5个任务。// 3、关闭线程池 :一般不关闭线程池。// pool.shutdown(); // 等所有任务执行完毕后再关闭线程池!// pool.shutdownNow(); // 立即关闭,不管任务是否执行完毕!}
}
线程池的注意事项
什么时候开始创建临时线程?
- 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会拒绝新任务? - 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
任务拒绝策略
策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 丢弃任务并抛出RejectedExecutionException异常。是默认的策略 |
ThreadPoolExecutor. DiscardPolicy() | 丢弃任务,但是不抛出异常,这是不推荐的做法 |
ThreadPoolExecutor. DiscardOldestPolicy() | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor. CallerRunsPolicy() | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 |
package com.itheima.demo7executorService;// 1、定义一个线程任务类实现Runnable接口
public class MyRunnable implements Runnable {// 2、重写run方法,设置线程任务@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "输出:" + i);try {// 线程休眠 Integer.MAX_VALUE值很大 // 可以认为永远沉睡Thread.sleep(Integer.MAX_VALUE);} catch (Exception e) {e.printStackTrace();}}}
}// Main
import java.util.concurrent.*;
public class ExecutorServiceDemo1 {public static void main(String[] args) {// 目标:创建线程池对象来使用。// 1、使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象。ExecutorService pool = new ThreadPoolExecutor(3, 5,10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());// 2、使用线程池处理任务!看会不会复用线程?Runnable target = new MyRunnable();pool.execute(target); // 提交第1个任务 创建第1个线程 自动启动线程处理这个任务pool.execute(target); // 提交第2个任务 创建第2个线程 自动启动线程处理这个任务pool.execute(target); // 提交第3个任务 创建第3个线程 自动启动线程处理这个任务pool.execute(target); // 提交第4个任务 第四个排队 new ArrayBlockingQueue<>(3)有三个座位供用户排队pool.execute(target); // 提交第5个任务 第五个排队 new ArrayBlockingQueue<>(3)有三个座位供用户排队pool.execute(target); // 提交第6个任务 第六个排队 此时等待的座位用完了,正式工也用完了。pool.execute(target); // 到了临时线程的创建时机了,招临时工1。pool.execute(target); // 到了临时线程的创建时机了,招临时工2。pool.execute(target); // 到了任务拒绝策略了,忙不过来// 如果使用线程池pool使用 new ThreadPoolExecutor.AbortPolicy(),会抛出异常,任务拒绝了。// 如果使用线程池pool使用 new ThreadPoolExecutor.CallerRunsPolicy(),会执行任务,但是不创建临时线程。// 如果使用线程池pool使用 new ThreadPoolExecutor.DiscardOldestPolicy(),会丢弃最老的任务,再执行新任务。// 如果使用线程池pool使用 new ThreadPoolExecutor.DiscardPolicy(),会丢弃新任务,不执行。// 如果使用线程池pool使用 new ThreadPoolExecutor.CallerRunsPolicy(),主线程main会执行任务,但是不创建临时线程。}
}
线程池如何处理Runnable任务?
使用ExecutorService的方法:
void execute(Runnable target)
处理Callable任务
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行 Runnable 任务 |
Future<T> submit(Callable<T> task) | 执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果 |
void shutdown() | 等全部任务执行完毕后,再关闭线程池! |
List<Runnable> shutdownNow() | 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务 |
总结
线程池如何处理Callable任务,并得到任务执行完后返回的结果?
使用ExecutorService的方法:
Future submit(Callable command)
import java.util.concurrent.Callable;
// 1、定义一个实现类实现Callable接口
public class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}// 2、实现call方法,定义线程执行体public String call() throws Exception {int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return Thread.currentThread().getName() +"计算1-" + n + "的和是:" + sum;}
}
// Main方法
import java.util.concurrent.*;
public class ExecutorServiceDemo2 {public static void main(String[] args) {// 目标:创建线程池对象来使用。// 1、使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象。ExecutorService pool = new ThreadPoolExecutor(3, 5,10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());// 2、使用线程池处理Callable任务!Future<String> f1 = pool.submit(new MyCallable(100));Future<String> f2 = pool.submit(new MyCallable(200));Future<String> f3 = pool.submit(new MyCallable(300));Future<String> f4 = pool.submit(new MyCallable(400));try {System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());//pool-1-thread-1计算1-100的和是:5050//pool-1-thread-2计算1-200的和是:20100//pool-1-thread-3计算1-300的和是:45150 // 线程池中的线程会自动复用。//pool-1-thread-2计算1-400的和是:80200} catch (Exception e) {e.printStackTrace();}}
}
方式二通过Executors工具类创建线程池
是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
方法名称 | 说明 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
public static ExecutorService newSingleThreadExecutor() | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
注意 :这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。
Executors使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
OOM内存溢出异常
Executors工具类底层是基于什么方式实现的线程池对象?
线程池ExecutorService的实现类:ThreadPoolExecutor
Executors是否适合做大型互联网场景的线程池方案?
不合适。
建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class ExecutorsDemo3 {public static void main(String[] args) {// 目标:通过线程池工具类:Executors,调用其静态方法直接得到线程池ExecutorService pool = Executors.newFixedThreadPool(3);Future<String> f1 = pool.submit(new MyCallable(100));Future<String> f2 = pool.submit(new MyCallable(200));Future<String> f3 = pool.submit(new MyCallable(300));Future<String> f4 = pool.submit(new MyCallable(400));try {System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());//pool-1-thread-1计算1-100的和是:5050//pool-1-thread-2计算1-200的和是:20100//pool-1-thread-3计算1-300的和是:45150 // 线程池中的线程3复用。//pool-1-thread-2计算1-400的和是:80200} catch (Exception e) {e.printStackTrace();}}
}
// 1、定义一个实现类实现Callable接口
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}// 2、实现call方法,定义线程执行体public String call() throws Exception {int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return Thread.currentThread().getName() +"计算1-" + n + "的和是:" + sum;}
}
在工作中,对于线程池的配置每个公司都不一样。
Java中的线程池的核心线程数是和最大线程数量的配置公式是什么样的?
在Java中,线程池的核心线程数量(corePoolSize)和最大线程数量(maximumPoolSize)的配置通常基于应用程序的特性来决定,比如它是CPU密集型还是IO密集型
CPU密集型任务
对于CPU密集型任务(计算密集型),线程池的大小通常设置为与系统可用处理器数量相等或稍多一点,以避免过多的上下文切换和减少线程调度的开销。公式如下:
int corePoolsize = Runtime.getRuntime().availableProcessors();
int maximumPoolSize=corePoolsize+1:// 或者等于 corePoolSize,根据具体需求调整
IO密集型任务
对于I0密集型任务(如网络清求、磁盘读写等),线程可能大部分时间都在等待IO操作完成,此时可以设置更多的线程来充分利用CPU等待的时间。公式如下:
- 核心线程数 =CPU核数 x(1/(1-阻塞系数)。
- 最大线程数 =核心线程数 x倍数(根据具体需求调整,例如2倍)
并发、并行
进程
正在运行的程序(软件)就是一个独立的进程。
线程是属于进程的,一个进程中可以同时运行很多个线程。
进程中的多个线程其实是并发和并行执行的。
并发的含义
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
并行的理解
在同一个时刻上,同时有多个线程在被CPU调度执行。
简单说说多线程是怎么执行的?
并发和并行同时进行的
并发:CPU分时轮询的执行线程。
并行:同一个时刻同时在执行。
抢红包 案例
红包雨游戏,某企业有100名员工,员工的工号依次是1,2,3, 4,…100。现在公司举办了年会活动,活动中有一个红包雨环节,要
求共计发出200个红包雨。其中小红包在[1-30]元之间,总占比为80%,大红包[31-100]元,总占比为20%。
具体的功能点如下
1、系统模拟上述要求产生200个红包。
2、模拟100个员工抢红包雨,需要输出哪个员工抢到哪个红包的过程,活动结束时需要提示活动结束。
3、活动结束后,请对100名员工按照所抢红包的总金额进行降序排序展示,例如:3号员工抢红包总计:293元、1号员工抢红包总计250元。
// PeopleGetRedPacket类
import java.util.List;// 线程类
public class PeopleGetRedPacket extends Thread{private List<Integer> redPacket;public PeopleGetRedPacket(List<Integer> redPacket, String name) {super(name);this.redPacket = redPacket;}@Overridepublic void run() {String name = Thread.currentThread().getName();while (true) {// 100个人来抢redPacket集合中的钱。synchronized (redPacket){if(redPacket.size() == 0){break;}// 随机一个索引得到红包int index = (int)(Math.random() * redPacket.size());Integer money = redPacket.remove(index);System.out.println(name + "抢到了" + money + "元");if(redPacket.size() == 0){System.out.println("活动结束!");break;}}try {Thread.sleep(2);} catch (Exception e) {e.printStackTrace();}}}
}// main
import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class ThreadTest {public static void main(String[] args) {// 目标:完成多线程的综合小案例// 红包雨游戏,某企业有100名员工,员工的工号依次是1, 2,3, 4,..到100。// 现在公司举办了年会活动,活动中有一个红包雨环节,要求共计发出200个红包雨。其中小红包在[1 - 30] 元之间,// 总占比为80%,大红包[31-100]元,总占比为20%。// 分析:100个员工实际上就是100个线程,来竞争200个红包。List<Integer> redPacket = getRedPacket();// 2、定义线程类,创建100个线程,竞争同一个集合。for (int i = 1; i <= 100; i++) {new PeopleGetRedPacket(redPacket, "人" + i).start();}}// 1、准备这200个随机的红包返回。放到List集合中去返回。public static List<Integer> getRedPacket() {Random r = new Random();// 其中小红包在[1 - 30] 元之间,总占比为80%,大红包[31-100]元,总占比为20%。List<Integer> redPacket = new ArrayList<>();for (int i = 1; i <= 160; i++) {redPacket.add(r.nextInt(30) + 1);}for (int i = 1; i <= 40; i++) {redPacket.add(r.nextInt(70) + 31);}return redPacket;}
}
相关文章:
java的多线程
文章目录 创建线程什么是线程?什么是多线程?如何在程序中创建出多条线程?方式一:继承Thread类方式二:实现Runnable接口方式三:实现Callable接口 三种创建方式的对比 线程的常用方法Thread提供的常用方法Thr…...
CSS--图片链接水平居中展示的方法
原文网址:CSS--图片链接居中展示的方法-CSDN博客 简介 本文介绍CSS图片链接水平居中展示的方法。 图片链接 问题复现 源码 <html xml:lang"cn" lang"cn"><head><meta http-equiv"Content-Type" content"te…...
【计算机视觉】目标检测:深度解析YOLOv5:下一代实时目标检测框架实战指南
深度解析YOLOv5:下一代实时目标检测框架实战指南 技术演进与架构设计YOLO系列发展脉络YOLOv5核心架构1. 骨干网络(Backbone)2. 特征融合(Neck)3. 检测头(Head) 环境配置与快速开始硬件要求建议详…...
CentOS NFS共享目录
最近遇到一个问题,一台CentOS7应用服务器上的服务需要访问另外一台CentOS7应用服务器上的文件,然后传输文件给第三方。想到windows系统之间有文件共享的功能,Linux系统之间是否也有类似的文件共享功能呢? NFS NFS代表Network Fil…...
「国产嵌入式仿真平台:高精度虚实融合如何终结Proteus时代?」——从教学实验到低空经济,揭秘新一代AI赋能的产业级教学工具
引言:从Proteus到国产平台的范式革新 在高校嵌入式实验教学中,仿真工具的选择直接影响学生的工程能力培养与创新思维发展。长期以来,Proteus作为经典工具占据主导地位,但其设计理念已难以满足现代复杂系统教学与国产化技术需求。…...
[随笔] 升级uniapp旧项目的vue、pinia、vite、dcloudio依赖包等
汇总 # 升级uniapp项目dcloudio整体依赖,建议执行多次 # 会顺带自动更新/升级vue的版本 npx dcloudio/uvmlatest alpha# 检查 pinia 的最新版本 npm view pinia version# 更新项目 pinia 到最新版本 npm update pinia# 更新项目 pinia 到特定的版本 # 首先…...
C++学习:六个月从基础到就业——异常处理:机制与最佳实践
C学习:六个月从基础到就业——异常处理:机制与最佳实践 本文是我C学习之旅系列的第三十八篇技术文章,也是第二阶段"C进阶特性"的最后一篇,主要介绍C中的异常处理机制及其最佳实践。查看完整系列目录了解更多内容。 引言…...
【MongoDB篇】MongoDB的数据库操作!
目录 引言第一节:数据库的“诞生”——如何创建数据库?🤔第二节:数据库的“查阅”——看看我的数据库们!🕵️♀️第三节:数据库的“切换”——我在哪个房间干活?➡️🚪…...
react-新建项目复用node_modules
每次新建定制时,前端都需要npm i来安装依赖,耗时长 失败多。 可以把这个bat文件放到新建分支的前端目录下,修改后双击bat文件运行,如果不需要添加修改依赖,无需运行npm i node_modules.bat里面的内容如下:…...
unity Orbbec Femto Bolt接入unity流程记录 AzureKinectExamples 插件 使用记录
奥比中光的深度相机Orbbec Femto Bolt是Microsoft的Azure Kinect DK的升级版,根据官网的文档配置环境遇到了一些问题,记录一下。 注意: 官网文档链接:Femto Bolt文档 1、首先连接相机到电脑USB3.0,接通电源…...
信息科技伦理与道德3-4:面临挑战
1 人机结合 1.1 人机结合的挑战 如何处理好人与机器的决策的关系?智能决策的不透明、不可解释性…出了问题该谁负责? 案例1:设想救护车调度系统造成混乱 某城市使用一个机器学习平台来进行城市里医院的救护车调度工作。起初,这个…...
对比测评:为什么AI编程工具需要 Rules 能力?
通义灵码 Project Rules 在开始体验通义灵码 Project Rules 之前,我们先来简单了解一下什么是通义灵码 Project Rules? 大家都知道,在使用 AI 代码助手的时候,有时候生成的代码不是自己想要的,或者说生成的代码采纳后…...
git学习之git常用命令
1. 初始化仓库 git init初始化一个新的 Git 仓库。 2. 克隆远程仓库 git clone <repository-url>从远程服务器克隆一个已有仓库到本地。 3. 配置用户名和邮箱 git config --global user.name "Your Name" git config --global user.email "youexampl…...
The Open Group 参加雷丁博物馆的数字革命展览
The Open Group 参加了雷丁博物馆的数字革命展览,庆祝雷丁市转型为数字中心60周年。 展览于3月18日(星期二)向公众开放,将持续至2025年12月24日。展览旨在纪念雷丁市令人惊叹的科技之旅,从1964年数字设备公司ÿ…...
Linux[配置vim]
Linux[配置vim] 我这里的环境是xshell8的虚拟机,Ubuntu 配置好了以后功能嘎嘎多 以下是为 Ubuntu 配置功能增强版 Vim 的详细步骤,包含代码高亮、插件管理、自动补全、文件导航等常用功能: 1. 安装最新版 Vim sudo apt update sudo apt install vim-g…...
【数据结构】图论存储结构深度解析:邻接多重表如何实现无向图O(1)删边?邻接矩阵/链表/十字链对比
邻接多重表 导读一、有向图的存储结构二、邻接多重表三、存储结构四、算法评价4.1 时间复杂度4.2 空间复杂度 五、四种存储方式的总结5.1 空间复杂度5.2 找相邻边5.3 删除边或结点5.4 适用于5.5 表示方式 六、图的基本操作结语 导读 大家好,很高兴又和大家见面啦&a…...
【AlphaFold2】Feature extraction:提取特征,为模型输入做准备|Datapipeline讲解
博主简介:努力学习的22级计算机科学与技术本科生一枚🌸博主主页: Yaoyao2024往期回顾:【深度学习】多头注意力机制的实现|pytorch每日一言🌼: 学习成绩只是表象,而学习能力才是伴随一身的结果🌺…...
Android 实现一个隐私弹窗
效果图如下: 1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数 2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来 res/layout/dialog_privacy_policy.xml 文件 <?xml version"1.0" encoding"utf-8"?&…...
第三方软件测试报告如何凭借独立公正与专业权威发挥关键作用?
在软件项目里,第三方软件测试报告起着极为关键的作用。第三方有着中立客观的立场。第三方具备专业能力。凭借这些,第三方能为软件质量评估提供可靠依据。下面要从不同方面介绍第三方软件测试报告。 独立公正性 第三方测试机构与软件开发方、使用方不存…...
QT控件 参考Qt的PIMPL设计模式实现使用QWidget控件绘制3D饼状图表和3D柱状图表,使用QChartView绘制圆柱体图表
整体绘制效果就是:Qt 实现3维饼状图 中的内容, 只不过我借鉴了Qt的PIMPL模式重新封装了整个实现过程 实现效果展示 目录导读 实现效果展示前言绘制3D饼状图表PIMPL模式设计类具体实现计算圆弧中心判断点是否在某个扇区中在私有类中绘制绘制3D柱状图表PIMPL模式设计类具体实现绘…...
Android Q允许低内存启用系统弹窗
如果SYSTEM_ALERT_WINDOW权限可用,则返回true。 *从Q开始,在低ram手机上禁用SYSTEM_ALERT_WINDOW。 vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/Utils.java public static boolean isSystemAlertWindowEnabled(Co…...
Leetcode 3532. Path Existence Queries in a Graph I
Leetcode 3532. Path Existence Queries in a Graph I 1. 解题思路2. 代码实现 题目链接:3532. Path Existence Queries in a Graph I 1. 解题思路 这一题算是一个比较典型的DSU的题目,我们就是不断地根据前后节点的距离将其进行聚类,然后…...
AI Agent Protocols:现状、挑战与未来展望
一、引言 在当今人工智能飞速发展的时代,大语言模型(LLMs)的进步使得LLM智能体在各个行业得到了广泛的应用,如客户服务、内容生成、数据分析和医疗保健等领域。 然而,随着越来越多的LLM智能体被部署,一个…...
自动化立库/AGV物流仿真详细步骤
以下是一种可以在预算和周期内实现自动化立库及AGV 方案仿真分析的方法: 一、工具选择 软件工具FlexSim:这是一款流行的离散事件仿真软件。它具有直观的图形用户界面,通过简单的拖拽操作就可以构建自动化立库和 AGV 的模型。其内置的丰富的…...
【题解-Acwing】872. 最大公约数
题目:872. 最大公约数 题目描述 给定 n 对正整数 ai,bi,请你求出每对数的最大公约数。 输入 第一行包含整数 n。 接下来 n 行,每行包含一个整数对 ai,bi。 输出 输出共 n 行,每行输出一个整数对的最大公约数。 数据范围 1 ≤ n ≤ 105, 1 ≤ai, bi ≤ 2109 时空限…...
62.微服务保姆教程 (五) Seata--微服务分布式事务组件
Seata–微服务分布式事务组件 一、什么是分布式事务 1.什么是事务 事务指的是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作都成功,要么所有的操作都被撤销。 2.本地事务 本地事务是指基于关系型数据库的事务,也称为传统事务。大多数场景…...
【算法练习】归并排序和归并分治
文章目录 1.归并排序1.1 递归版本1.2 非递归版本 2.归并分治2.1 计算数组的小和2.2 计算翻转对 1.归并排序 归并排序的核心步骤是: 拆分:将无序数组不断对半拆分成小块,直到每个小块只剩一个元素(自然有序)。 合并&a…...
从SOA到微服务:架构演进之路与实践示例
一、架构演进背景 在软件开发领域,架构风格随着业务需求和技术发展不断演进。从早期的单体架构,到面向服务架构(SOA),再到如今的微服务架构,每一次变革都是为了解决当时面临的核心问题。 二、SOA架构解析 2.1 SOA核心概念 SOA&…...
vue+cesium线流动纹理
index.vue页面 <!--线流动纹理实现--> <template><div id"mapContainerFirst"></div> </template> <script lang"ts" setup> import { init as initPolylineTrailLinkMaterialProperty } from ./PolylineTrailLinkM…...
深度学习·经典模型·SwinTransformer
SwinTransformer 主要创新点:移动窗口,基于窗口的注意力计算 Patch Embedding 下采样打包为Pacth:可以直接使用Conv2d 也可以先打包后使用embedding映射。 Patch Merging 类似池化的操作,压缩图片大小,同时通道数增多ÿ…...
在开发板上如何处理curl: (60) SSL certificate problem
目录 引言 问题解析 解决方法 跳过证书验证 采用证书认证 结语 引言 最近一直推荐学生们在课程实验中使用curl及其libcurl。curl 是一个强大的命令行工具,用于在命令行中进行数据传输。它支持多种协议,如 HTTP、HTTPS、FTP、FTPS、SCP、SFTP 等。…...
Ansible 铸就 Linux 安全之盾(Ansible Builds Linux Security Shield)
Ansible 铸就 Linux 安全之盾:自动化基线检查与防护 在当今网络安全形势日益严峻的背景下,Linux 系统作为服务器和关键基础设施的核心,其安全防护显得尤为重要。Ansible 作为一款强大的自动化运维工具,能够帮助我们高效、可靠地实…...
字符串(格式化字符串字面值)进行输出
在 Python 中,print(fnew_obs:{new_obs}) 这种形式是使用 f 字符串(格式化字符串字面值) 进行输出,它可以打印 任何可转换为字符串的数据类型,并且支持在字符串中嵌入表达式。以下是详细说明: 1. 基本功能…...
微服务架构详解:从概念到实践
目录 前言1. 微服务架构概述1.1 什么是微服务?1.2 微服务的核心思想 2. 微服务的优势2.1 可扩展性2.2 高灵活性2.3 容错性和可靠性2.4 高效开发与部署 3. 微服务的挑战3.1 系统复杂性增加3.2 分布式事务和数据一致性3.3 部署和运维的复杂性 4. 微服务的实施与实践4.…...
激光驱鸟:以科技重构生态防护边界
技术原理 激光驱鸟装置的核心机制基于鸟类视觉系统特性。其发射的绿色激光束(波长通常为532纳米)处于鸟类视网膜敏感光谱范围内,当激光束在特定角度扫描时,会形成动态光斑干扰。鸟类视网膜中视锥细胞对绿色光的高敏感度使其产生应…...
【Python魔法方法(特殊方法)】
在 Python 中,许多运算符都可以进行重载,以下是一些常见运算符及其对应的魔法方法(特殊方法): 算术运算符 加法 :__add__ 用于定义对象相加的行为。例如,当你对两个自定义类的实例使用 运算符…...
centos上安装python的3.13版本
在 CentOS 上安装 Python 3.13(或其它自定义版本)最推荐的方法是通过源码编译安装,不会影响系统自带的 Python2/Python3 环境,也更灵活可控。 以下步骤适用于: ✅ CentOS 7 / 8 / 9 ✅ 安装 Python 3.13(…...
实习技能记录【4】-----消息分发中的观察者模型
观察者 观察者模式(Observer Pattern)是一种行为型设计模式,主要用于定义对象之间的一对多依赖关系,让多个观察者对象能够同时监听某个主题对象的状态变化,并在主题对象状态改变时自动通知所有观察者对象。 参考b站博…...
Linux 下编译BusyBox
一、linux下编译 1.拉取busybox源码 git clone https://github.com/mirror/busybox.git 内容如下 2.配置make,建议在linux下单独开一个终端执行 进入busybox源码目录,使用如下命令 make menuconfig 3.报错 解决办法: 安装ncurses sud…...
Linux《进程概念(中)》
在之前的Linux《进程概念(上)》当中我们已经了解了进程的基本概念以及如何去创建对应的子进程,那么接下来在本篇当中我们就继续来进程的学习,在本篇当中我们要学习到进程的状态、进程的优先级、进程切换、Linux真实的调度算法——…...
Linux Vim 使用 显示行号、替换、查找、多文件打开等骚操作
目录 简述 vim的三种模式 概述 转换方式 文本编辑 命令模式 插入(编辑)模式 底行模式 搜索关键字 显示行号 替换 多文件打开 简述 vi编辑器是Linux系统下标准的编辑器。 那么简单的理解,就像是Windows下的记事本。 补充&a…...
AimRT 从零到一:官方示例精讲 —— 三、Executor示例.md
Executor示例 官方仓库:executor 配置文件(configuration_executor.yaml) 依据官方示例项目结构自行编写YAML配置文件: # 基础信息 base_info:project_name: Logger # 项目名称build_mode_tags: ["EXAMPLE", &quo…...
只把夜莺监控当作告警来使用:一种轻量化的运维实践
只把夜莺监控当作告警来使用:一种轻量化的运维实践 在现代的 IT 运维体系中,监控和告警是两个经常被一同提及的概念。然而,在实际工作中,很多团队对监控系统的需求并不一定全面覆盖指标采集、可视化展示、告警触发等功能…...
按键精灵安卓ios辅助工具脚本:实用的文件插件(lua开源)
亮点:此lua插件可再android和ios上通用 1、获取文件的属性 2、改变当前的工作路径为dirpath 3、获取当前的工作路径 4、创建文件夹,支持多级创建 5、删除文件夹 6、递归遍历文件夹 7、设置文件的访问时间和修改时间 函数原型:lfs.Attribute(…...
水库现代化建设指南-水库运管矩阵管理系统建设方案
政策背景 2023年8月24日,水利部发布的水利部关于加快构建现代化水库运行管理矩阵的指导意见中指出,在全面推进水库工程标准化管理的基础上,强化数字赋能,加快构建以推进全覆盖、全要素、全天候、全周期“四全”管理,完…...
若依后台管理系统-v3.8.8-登录模块--个人笔记
各位编程爱好者们,你们好!今天让我们来聊聊若依系统在登录模块的一些业务逻辑,以及本人的一些简介和心得,那么废话不多说,让我们现在开始吧。 以下展示的这段代码,正是若依在业务层对应的登录代码…...
Flip PDF Plus Corp7.7.22电子书制作软件
flip pdf plus corporate7.7.22中文版由FlipBuilder官方出品的一款企业级的翻页电子书制作软件,拥有丰富的模板,主题和动画场景,每本书最大页数1000页,每本书的最大大小1GB,即可以帮助企业用户制作好丰富的电子书籍。 …...
公路安全知识竞赛主持稿串词
合 :尊敬的各位领导、各位来宾 、各位选手 : 大家上午 好! 男 :安全就是生命,安全就是效益,安全是一切工作的重中之重!安全生产只有满分,没有及格。只有安全生产这个环节不出差错,我…...
vscode 配置qt
工具:vscode、qttools、qtconfigure Search Mode改成基于cmake的。 # 在项目中指定Qt的路径 set(Qt5_DIR "/home/jp/qt-everywhere-src-5.12.9/arm-qt/lib/cmake/Qt5") # 用于指定 Qt5 的安装路径 find_package(Qt5 REQUIRED COMPONENTS Widgets)这样就…...
Node.js 事件循环和线程池任务完整指南
在 Node.js 的运行体系中,事件循环和线程池是保障其高效异步处理能力的核心组件。事件循环负责调度各类异步任务的执行顺序,而线程池则承担着处理 CPU 密集型及部分特定 I/O 任务的工作。接下来,我们将结合图示,详细剖析两者的工作…...