多线程万字详解

进程线程是计算机程序执行的两个重要概念。

1.进程: 进程是操作系统分配资源的基本单位,每个进程都有自己独立的地址空间,每启动一个进程,系统就会为它分配内存。进程间通信比较复杂,需要用到IPC(InterProcess Communication,进程间通信)机制。

进程有五种状态:创建、就绪、运行、阻塞和终止。

2.线程: 线程是进程中的一个执行单元,一个进程可以包含多个线程,它们共享进程的地址空间和资源。线程间通信比进程间通信要简单很多,因为它们可以直接读写进程数据段(如全局变量)来进行通信。

线程也有五种状态:创建、就绪、运行、阻塞和终止。

3.进程和线程的区别

  • 独立性:进程间的内存空间是独立的,而线程共享进程的内存空间。

  • 资源消耗:创建或销毁进程时,系统性能开销明显,而线程的资源消耗远小于进程。

  • 通信方式:进程间通信需要使用IPC机制,而线程可以直接读写全局变量进行通信。

  • 影响:一个进程崩溃后,其他进程不受影响;而一个线程崩溃,会导致整个进程崩溃。

4.多线程和多进程的选择: 在需要进行频繁通信的情况下,多线程更有优势,因为它们可以直接读写内存进行通信。而在需要大量计算且相互独立的情况下,多进程可能更合适,因为它们不会因为一个进程的崩溃而影响其他进程。

并发和并行

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

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

实现多线程方式一:继承Thread类

方法介绍
方法名说明
void run()在线程开启后,此方法将被调用执行
void start()使此线程开始执行,Java虚拟机会调用run方法()

实现步骤

  • 定义一个类MyThread继承Thread类

  • 在MyThread类中重写run()方法

  • 创建MyThread类的对象

  • 启动线程

  • 代码演示

public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+"Hello World");}}
}
public class ThreadDemo {public static void main(String[] args) {/*多线程第一种启动方式1.自己定义一个类继承Thread2.重写run方法3.创建子类的对象,并启动线程*/MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("线程一");t2.setName("线程二");//开启线程t1.start();}
}

 

实现多线程方式二:实现Runnable接口

Thread构造方法

方法名说明
Thread(Runnable target)分配一个新的Thread对象
Thread(Runnable target, String name)分配一个新的Thread对象

 

实现步骤

  • 定义一个类MyRunnable实现Runnable接口

  • 在MyRunnable类中重写run()方法

  • 创建MyRunnable类的对象

  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

  • 启动线程

public class MyRun implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {//获取到当前线程的对象Thread t = Thread.currentThread();System.out.println(t.getName()+"Hello World");}}
}
public class ThreadDemo {public static void main(String[] args) {/*多线程第二种启动方式1.自己定义一个类继承Runnable接口2.重写run方法3.创建自己类的对象4.创建一个Thread类的对象,并开启线程*///创建MyRun的对象//表示多线程要执行的任务MyRun mr = new MyRun();//创建线程对象Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.setName("线程一");t2.setName("线程二");//开启线程t1.start();}
}

 

实现多线程方式三: 实现Callable接口

方法介绍

方法名说明
V call()计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable)创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()如有必要,等待计算完成,然后获取其结果

 

实现步骤

  • 定义一个类MyCallable实现Callable接口

  • 在MyCallable类中重写call()方法

  • 创建MyCallable类的对象

  • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数

  • 创建Thread类的对象,把FutureTask对象作为构造方法的参数

  • 启动线程

  • 再调用get方法,就可以获取线程结束之后的结果。

代码演示

public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= 100; i++) {sum = sum + i;}return sum;}
}

 

public class ThreadDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {/*多线程的第三种实现方式特点:可以获取到多线程运行到的结果1.创建一个类MyCallable实现Callable接口2.重写call(有返回值,表示多线程运行的结果)3.创建MyCallable的对象(表示多线程要执行的任务)4.创建FutureTask的对象(作用管理多线程运行的结果)5.创建Thread类的对象,并启动*/MyCallable mc = new MyCallable();FutureTask<Integer> ft = new FutureTask(mc);Thread thread = new Thread(ft);thread.start();//获取多线程运行的结果Integer result = ft.get();System.out.println(result);}
}

 Java中多线程的三种主要实现方式各有其特点和适用场景。具体来说:

  • 继承Thread类:通过继承Thread类并重写run方法来实现多线程,这种方法简单直观,但在Java的单继承体系下,可能会导致类的扩展性受限。
  • 实现Runnable接口:通过实现Runnable接口并将该实现类的实例传递给Thread对象来创建线程,这种方式避免了单继承的限制,提高了代码的可读性和可维护性。
  • 实现Callable接口:与Runnable类似,但可以返回执行结果,通常与FutureTask结合使用,允许线程执行完毕后获取返回值,增加了线程的使用场景。

总的来说,如果需要简单的线程逻辑并且不需要额外的返回结果,可以选择继承Thread类或实现Runnable接口。而当需要更复杂的线程管理和控制,或者希望利用线程池等高级特性时,应该考虑使用Executor框架。

线程休眠

相关方法

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数

 代码演示

public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "---" + i);}}
}
public class Demo {public static void main(String[] args) throws InterruptedException {/*System.out.println("睡觉前");Thread.sleep(3000);System.out.println("睡醒了");*/MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.start();t2.start();}
}

线程优先级

线程调度

在Java中,除了上述两种基本的调度方式,还可以通过编程手段对线程的执行顺序进行一定程度的控制。例如,可以在需要等待或者后执行的线程中加入sleep()方法,使其在进入CPU调度初期就进入休眠状态,从而给其他线程执行的机会。

优先级相关方法代码演示

  • 线程调度是指在多线程环境中,操作系统或运行时环境如何分配CPU资源给各个线程的过程。

    线程调度主要有两种调度方式:

  • 分时调度:这种调度方式下,CPU的时间被平均分配给所有线程。每个线程轮流获得CPU的使用权,执行一段时间后再切换到下一个线程。这种方式简单公平,但可能不是最高效的方式,因为它不考虑线程的优先级和实际需求。
  • 抢占式调度:这种调度方式会根据线程的优先级来决定哪个线程获得CPU的使用权。优先级高的线程会得到更多的执行机会。如果多个线程的优先级相同,通常会随机选择一个线程来执行。Java使用的就是抢占式调度,这种方式能够更好地满足高优先级任务的需求。
方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "---" + i);}return "线程执行完毕了";}
}

 

public class Demo {public static void main(String[] args) {//优先级: 1 - 10 默认值:5MyCallable mc = new MyCallable();FutureTask<String> ft = new FutureTask<>(mc);Thread t1 = new Thread(ft);t1.setName("飞机");t1.setPriority(10);//System.out.println(t1.getPriority());//5t1.start();MyCallable mc2 = new MyCallable();FutureTask<String> ft2 = new FutureTask<>(mc2);Thread t2 = new Thread(ft2);t2.setName("坦克");t2.setPriority(1);//System.out.println(t2.getPriority());//5t2.start();}

守护线程

相关方法代码演示

方法名说明
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class MyThread1 extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + "---" + i);}}
}
public class MyThread2 extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + "---" + i);}}
}
public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();MyThread2 t2 = new MyThread2();t1.setName("女神");t2.setName("备胎");//把第二个线程设置为守护线程//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.t2.setDaemon(true);t1.start();t2.start();}
}

线程的生命周期

线程同步

卖票

案例需求

电影院卖100张票,

而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

实现步骤

public class SellTicket extends Thread {static int ticket = 0;@Overridepublic void run() {while (true) {if (ticket < 100) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正在卖" + ticket + "票");} else {break;}}}
}
public class ThreadDemo {                       public static void main(String[] args) {/*某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票*/SellTicket s1 = new SellTicket();SellTicket s2 = new SellTicket();SellTicket s3 = new SellTicket();s1.setName("窗口一");s2.setName("窗口二");s3.setName("窗口三");s1.start();s2.start();s3.start();}}

卖票案例的问题

  • 卖票出现了问题

    • 相同的票出现了多次

    • 出现了多的票

  • 问题产生原因

    线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

同步代码块解决数据安全问题

  • 安全问题出现的条件

    • 是多线程环境

    • 有共享数据

    • 有多条语句操作共享数据

  • 如何解决多线程安全问题呢?

    • 基本思想:让程序没有安全问题的环境

  • 怎么实现呢?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

    • Java提供了同步代码块的方式来解决

同步代码块格式:

synchronized(任意对象) { 多条语句操作共享数据的代码 
}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端

  • 好处:解决了多线程的数据安全问题

  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

代码演示

public class SellTicket extends Thread {static int ticket = 0;//锁对象,一定是唯一的@Overridepublic void run() {synchronized (SellTicket.class){while (true) {if (ticket < 100) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正在卖" + ticket + "票");} else {break;}}}}
}

同步方法解决数据安全问题

同步方法的格式

同步方法:就是把synchronized关键字加到方法上

修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体;
}

同步静态方法的锁对象是什么呢?

类名.class

非静态:this

同步静态方法的锁对象是当前类的Class对象

在Java中,当使用synchronized关键字修饰一个静态方法时,这意味着这个方法被同步化,并且所有线程在访问这个方法时必须先获得锁。对于静态同步方法,这个锁就是当前类的Class对象。这与其他非静态同步方法使用的锁不同,后者使用的是实例对象作为锁。

具体来说,当一个线程尝试访问一个类的静态同步方法时,它会尝试获取该类对应的Class对象的锁。如果该锁已被其他线程持有,则当前线程必须等待直到锁被释放。这种机制确保了同一时刻只有一个线程能够执行同一个类的任何静态同步方法。

此外,静态同步方法与非静态同步方法之间不会发生竞态条件,因为它们使用的是不同的锁对象。静态同步方法使用的是类对象本身的锁,而非静态同步方法使用的是实例对象的锁。

了解同步静态方法的锁对象对于编写多线程程序和理解Java内存模型中的线程安全和并发控制非常重要。

Lock锁

Lock锁是Java并发编程中的一种同步机制,它提供了比synchronized关键字更加灵活的锁操作

首先,Lock锁的优势在于它能够提供更广泛的锁操作。与synchronized相比,Lock锁可以实现更细粒度的锁控制,例如可重入锁、公平锁等。具体来说:

  • 可重入锁:允许同一个线程多次获得锁,而不会导致自己被阻塞。
  • 公平锁:保证等待时间最长的线程能够先获得锁,避免线程饥饿现象。
  • 读写锁:允许多个读线程同时访问,但在写线程访问时会独占锁。

此外,Lock锁的使用通常需要手动释放,这要求开发者在finally块中释放锁以确保资源的正确释放。而synchronized则不需要手动释放。

总的来说,Lock锁提供了更多的功能和灵活性,但也带来了更高的复杂性。在使用Lock锁时,需要注意正确管理锁的获取和释放,以避免死锁或资源泄露。

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock构造方法

方法名说明
ReentrantLock()创建一个ReentrantLock的实例

加锁解锁方法

方法名说明
void lock()获得锁
void unlock()释放锁

代码演示

  public class Ticket implements Runnable {//票的数量private int ticket = 100;private Object obj = new Object();private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {//synchronized (obj){//多个线程必须使用同一把锁.try {lock.lock();if (ticket <= 0) {//卖完了break;} else {Thread.sleep(100);ticket--;System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}// }}}}

死锁

死锁是指两个或多个执行单元(例如进程、线程或事务)在等待彼此持有的资源时,导致它们都无法 progress。产生死锁的情况通常涉及以下四个方面:

  1. 互斥条件:指资源至少由一个执行单元持有,且在释放之前其他执行单元无法使用。
  2. 占有和等待条件:指执行单元已经持有至少一个资源,但又提出了新的资源请求,被阻塞的执行单元仍然保持着它的资源。
  3. 不剥夺条件:指一个执行单元获得的资源在未使用完之前不能被强行剥夺,即使该资源有可能满足其他等待资源的执行单元的需求。
  4. 循环等待条件:存在一种环形链,每个执行单元都在等待下一个执行单元所占有的资源。

在实际开发中,为了避免死锁,可以采取一些策略,如尽量减少事务的大小和持续时间,避免长时间占用锁资源,以及尽量保持事务之间访问资源的一致性和顺序,避免交叉锁定。

下面是可以产生死锁的一段代码:~

public class DeadlockDemo {private static Object lock1 = new Object();private static Object lock2 = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized (lock1) {System.out.println("Thread 1: Holding lock 1...");try {Thread.sleep(100); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 1: Waiting for lock 2...");synchronized (lock2) {System.out.println("Thread 1: Holding lock 1 & 2...");}}});Thread thread2 = new Thread(() -> {synchronized (lock2) {System.out.println("Thread 2: Holding lock 2...");try {Thread.sleep(100); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 2: Waiting for lock 1...");synchronized (lock1) {System.out.println("Thread 2: Holding lock 1 & 2...");}}});thread1.start();thread2.start();}
}

生产者消费者

概述

生产者消费者模式是一种经典的多线程协作模型,它涉及两类线程:生产者和消费者。

生产者:负责生产数据或对象,并将它们放入一个共享的队列中。生产者不直接与消费者交互,而是将产品放入队列后继续生产,这样可以保证生产者的效率不会因为等待消费者而降低。 消费者:负责从队列中取出数据或对象并进行消费。消费者不需要等待生产者生产,只需检查队列中是否有产品可消费。这样,消费者可以保持持续的工作状态,提高整体的处理效率。

此外,在实现生产者消费者模式时,通常需要考虑以下几个关键点:

  • 共享队列:生产者和消费者通过这个队列进行间接通信。生产者将产品放入队列,消费者从队列中取出产品。
  • 同步机制:需要确保当队列满时,生产者停止生产;当队列空时,消费者停止消费。这通常通过使用信号量、锁或其他同步机制来实现。
  • 线程通信:在某些情况下,生产者生产了新的产品后,需要通知消费者来消费。这种通信可以通过条件变量或其他同步工具来实现。

总的来说,生产者消费者模式有效地解决了生产者和消费者之间的速度匹配问题,允许两者以不同的速度运行,同时确保数据的安全传输和处理。

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程

为方便理解采用以下示例

来源://https://www.bilibili.com/video/BV17F411T7Ao/

桌子

public class Desk {/*作用:控制生产者和消费者的执行*///是否有面条 0:没有 1:有面条public static int foodFlag = 0;//总个数public static int count = 10;//锁对象public static Object lock = new Object();}

生产者

public class Cook extends Thread{@Overridepublic void run() {while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else {//判断桌子上是否有食物if(Desk.foodFlag == 1){//如果有,就等待try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {//如果没有,就制作食物System.out.println("厨师做了一碗面条");//修改桌子上的食物状态Desk.foodFlag = 1;//叫醒等待的消费者开吃Desk.lock.notifyAll();}}}}}
}

消费者

public class Foodie extends Thread{@Overridepublic void run() {while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else {if(Desk.foodFlag == 0){//用锁的对象调用wait()try {//让当前线程跟锁进行绑定Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {Desk.count--;//如果有,就开吃System.out.println("吃货正在吃面条,还能再吃"+Desk.count+"碗!!!");//吃完之后,唤醒厨师继续做Desk.lock.notify();Desk.foodFlag = 0;}}}}}
}

二:阻塞队列完成等待唤醒机制

生产者

public class Cook extends Thread{ArrayBlockingQueue<String> queue;public Cook(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while(true){//不断的把面条放在阻塞队列当中try {queue.put("面条");System.out.println("厨师放了一碗面条");} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Foodie extends Thread{ArrayBlockingQueue<String> queue;public Foodie(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while(true){//不断从阻塞队列中获取面条try {String food = queue.take();System.out.println(food);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo {public static void main(String[] args) {/*利用阻塞队列完成生产者消费者(等待唤醒机制)//细节:生产者消费者必须使用同一个阻塞队列*///1.创建阻塞队列的对象ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);//创建线程的对象,并把阻塞队列传递过去Cook c = new Cook(queue);Foodie f = new Foodie(queue);c.start();f.start();}
}

线程状态

线程池

线程池是一种用于管理线程的资源池,它可以显著提高多线程程序的性能和可靠性

线程池的实现基于生产者-消费者模式,任务提交方作为生产者将任务放入线程池,而线程池中的工作线程作为消费者来执行这些任务。以下是线程池的一些关键特点:

  1. 线程复用:线程池通过重用现有线程来执行新任务,减少了频繁创建和销毁线程所带来的开销。
  2. 任务管理:线程池内部通常有一个工作队列,用于存储等待执行的任务,这样可以实现任务的有序执行。
  3. 性能提升:通过减少线程创建的开销和优化线程调度,线程池能够提高程序的响应速度和吞吐量。
  4. 资源控制:线程池可以限制同时运行的线程数量,防止系统过载,同时也可以减少上下文切换的开销。
  5. 编程简化:使用线程池可以简化多线程编程,开发者只需关注任务的实现,而不必处理线程的创建和管理细节。
  6. 异步执行:线程池支持异步执行任务,可以在不阻塞主线程的情况下执行耗时操作,提升用户体验。
  7. 灵活性:现代编程语言如C++提供了丰富的API和库来支持线程池的实现,使得开发者可以根据需要定制线程池的行为。
  8. 并发控制:线程池可以帮助开发者更好地控制并发级别,避免资源竞争和死锁等问题。

综上所述,线程池是一种高效的线程管理机制,它通过池化技术优化了线程的创建、管理和调度,为并发编程提供了强大支持。在实际应用中,合理地使用线程池可以带来显著的性能提升和更好的资源利用率。

线程池代码实现

  1. 创建线程池

  2. 提交任务

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

线程池-Executors默认线程池

我们可以使用Executors中所提供的静态方法来创建线程池

static ExecutorService newCachedThreadPool() 创建一个默认的线程池 ​ static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池

public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100; i++) {System.out.println(Thread.currentThread().getName()+"---"+ i);}}
}
public class test8 {public static void main(String[] args) {//获取线程池对象ExecutorService pool1 = Executors.newCachedThreadPool();//ExecutorService pool1 = Executors.newCachedThreadPool(3);//提交任务pool1.submit(new MyRunnable());//销毁线程池/* pool1.shutdown();*/}
}

线程池-参数详解

Java中的线程池通过ThreadPoolExecutor类实现,它提供了灵活的参数配置来满足不同场景的需求。以下是该类构造方法中的七个主要参数及其作用:

  1. corePoolSize:这是线程池的核心线程数量,即线程池中始终保持的最小线程数。即使这些线程处于空闲状态,也不会被销毁,除非设置了allowCoreThreadTimeOut属性。
  2. maximumPoolSize:这是线程池允许创建的最大线程数量。当任务数量超过核心线程数时,线程池会尝试创建新的线程来处理任务,但总数不会超过这个参数指定的值。
  3. keepAliveTime:当线程池中的线程数量超过核心线程数时,多余的空闲线程在被销毁之前可以存活的时间。这个参数可以帮助控制资源使用,避免长时间持有不必要的线程资源。
  4. unit:与keepAliveTime参数配合使用,用于指定keepAliveTime的时间单位,如TimeUnit.SECONDS表示秒。
  5. workQueue:用于存放待执行任务的阻塞队列。不同的队列实现有不同的特性,如ArrayBlockingQueueLinkedBlockingQueue等,选择合适的队列对线程池的性能有重要影响。
  6. threadFactory:用于创建新线程的工厂。可以通过实现ThreadFactory接口来自定义线程的创建过程,如设置线程名称、守护状态等。
  7. handler:当线程池和队列都满了,无法接受新任务时的拒绝策略。常见的策略有AbortPolicy(默认,抛出异常)、CallerRunsPolicy(调用者运行)和DiscardOldestPolicy(丢弃最旧任务)等。

综上所述,合理配置这些参数对于提高线程池的效率和稳定性至关重要。例如,根据任务的特性和系统的资源状况来调整核心线程数和最大线程数,以及选择适合的拒绝策略,可以在保证性能的同时避免资源的过度消耗

为方便理解看下图 图源://​​黑马程序员Java零基础视频教程_上部(Java入门,含斯坦福大学练习题+力扣算法题和大厂java面试题)_哔哩哔哩_bilibili

 

public class test8 {public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(3,//核心线程数量,不能小于06,//最大线程数,不能小于0,最大数量 >= 核心线程数量60, //空闲线程最大存活时间TimeUnit.SECONDS,//时间单位new ArrayBlockingQueue<>(3),//任务队列Executors.defaultThreadFactory(),Executors.defaultThreadFactory(),//创建线程工厂new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/712114.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AttributeError_ ‘list‘ object has no attribute ‘view‘

问题描述 训练yolov9的时候遇到了下面的问题。 In loss_tal.py: pred_distri, pred_scores torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split( (self.reg_max * 4, self.nc), 1) The error is as follows&#xff1a; AttributeError: list …

JavaWeb之 Web概述

目录 前言1.1 Web和 JavaWeb的概念1.2 JavaWeb技术栈1.2.1 B/S架构1.2.2 静态资源1.2.3 动态资源1.2.4 数据库1.2.5 HTTP协议1.2.6 Web服务器 1.3 JavaWeb 学习内容 前言 博主将用 CSDN 记录 Java 后端开发学习之路上的经验&#xff0c;并将自己整理的编程经验和知识分享出来&a…

【Web自动化测试——代码篇十二】自动化测试模型——数据驱动测试和关键字驱动测试

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

理解C#里面的集合有哪些?怎么用,什么是安全集合?

介绍 在C#中&#xff0c;集合是一种用于存储和操作多个元素的数据结构。它们提供了各种操作&#xff0c;如添加、删除、查找等&#xff0c;以及遍历集合中的元素。集合通常根据其实现方式和行为特征进行分类。 集合继承IEnumerable 在C#中&#xff0c;几乎所有的集合类型都实现…

简历中自我评价,是否应该删掉?

你好&#xff0c;我是田哥 年后&#xff0c;不少朋友已经开始着手准备面试了&#xff0c;准备面试的第一个问题就是&#xff1a;简历。 写简历是需要一些技巧的&#xff0c;你的简历是要给面试官看&#xff0c;得多留点心。 很多简历上都会写自我评价/个人优势/个人总结等&…

2024有哪些免费的mac苹果电脑深度清理工具?CleanMyMac X

苹果电脑用户们&#xff0c;你们是否经常感到你们的Mac变得不再像刚拆封时那样迅速、流畅&#xff1f;可能是时候对你的苹果电脑进行一次深度清理了。在这个时刻&#xff0c;拥有一些高效的深度清理工具就显得尤为重要。今天&#xff0c;我将介绍几款优秀的苹果电脑深度清理工具…

一个Web3项目的收官之作,必然是友好的用户界面(Web3项目三实战之四)

正如标题所述,一个对用户体验友好的应用,总是会赢得用户大加赞赏,这是毋庸置疑的。 甭管是web2,亦或是已悄然而至的Web3,能有一个外观优美、用户体验效果佳的的界面,那么,这个应用无疑是个成功的案例。 诚然,Web3项目虽然核心是智能合约攥写,但用户界面也是一个DApp不…

【Leetcode每日一刷】哈希表|纲领、242.有效的字母异位词、349. 两个数组的交集

纲领 &#x1f517;代码随想录理论部分 关于哈希表这个数据结构就不再重复讲了&#xff0c;下面对几个关键点记录一下&#xff1a; 哈希碰撞 解决方法1&#xff1a;拉链法 解决方法2&#xff1a;线性探测法 下面针对做题要用到的三种结构讲一下&#xff08;也是重复造轮子了…

vue.config.js publicPath 和 vue-router base 结合配置项目根目录为二级目录案例

背景: 同个域名下需要有 PC 管理后台, H5 端, 企业微信 ......等多个端, 需要在一个域名下通过不同的路径来区分不同的项目; 例如: abc.com/pc, abc.com/h5, abc.com/wx-work.... 此处做个记录 步骤: 1. 修改 vue.config.js 中的 publicPath module.exports {outputDir:…

MATLAB|【免费】概率神经网络的分类预测--基于PNN的变压器故障诊断

目录 主要内容 部分代码 结果一览 下载链接 主要内容 ​《MATLAB神经网络43个案例分析》共有43章&#xff0c;内容涵盖常见的神经网络&#xff08;BP、RBF、SOM、Hopfield、Elman、LVQ、Kohonen、GRNN、NARX等&#xff09;以及相关智能算法&#xff08;SVM、决策…

Java 下载excel文件

一、背景 微信小程序需要导出excel文件&#xff0c;后端技术Java&#xff0c;前端使用uniapp框架&#xff0c;使用excel模板。 二、excel 报表模板 需要补充的内容是以下标记问号的&#xff0c;其中有个表格&#xff0c;内容是动态添加的 三、Java端代码实现 关键步骤&…

Topaz Video AI:一键提升视频品质,智能重塑影像魅力 mac/win版

Topaz Video AI是一款革命性的视频智能处理软件&#xff0c;它利用先进的机器学习和人工智能技术&#xff0c;为视频创作者提供了前所未有的视频增强和修复功能。无论您是专业视频编辑师、摄影师&#xff0c;还是热爱视频创作的爱好者&#xff0c;Topaz Video AI都能帮助您轻松…

Python程序的流程

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 年轻是我们唯一拥有权利去编制梦想的时…

【前端素材】推荐优质后台管理系统Annex平台模板(附源码)

一、需求分析 1、系统定义 后台管理系统是一种用于管理网站、应用程序或系统的管理界面&#xff0c;通常由管理员和工作人员使用。它提供了访问和控制网站或应用程序后台功能的工具和界面&#xff0c;使其能够管理用户、内容、数据和其他各种功能。 2、功能需求 后台管理系…

利用python爬取本站的所有博客链接

目录 前因 首先的尝试 解决办法 导入包 定义一个json配置文件 打开浏览器执行操作 注意 提取源代码并且进行筛选链接 执行结果 前因 由于自己要把csdn的博客同步到hugo中&#xff0c;把博客转为md格式已经搞好了&#xff0c;但是由于csdn的图片具有防盗链&#xff0c;…

2024年经典【自动化面试题】附答案

一、请描述一下自动化测试流程&#xff1f; 自动化测试流程一般可以分为以下七步&#xff1a; 编写自动化测试计划&#xff1b; 设计自动化测试用例&#xff1b; 编写自动化测试框架和脚本&#xff1b; 调试并维护脚本&#xff1b; 无人值守测试&#xff1b; 后期脚本维…

【数据结构】深入探讨二叉树的遍历和分治思想(一)

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;数据结构 &#x1f525;该文章主要讲述二叉树的递归结构及分治算法的思想。 目录&#xff1a; &#x1f30d;前言&#xff1a;&#x1f30d;…

Sora 原理与技术实战笔记一

b 站视频合集 【AIX组队学习】Sora原理与技术实战&#xff1a;Sora技术路径详解 Sora 技术报告&#xff08;OpenAI&#xff09; huggingsd 文生图视频系列的一个开源项目 最强视频生成模型Sora相关技术解析 https://github.com/lichao-sun/SoraReview 惊艳效果&#xff1a; 长…

云呐智能运维包含哪些内容?运维未来的发展方向是什么?

智能运维&#xff08;AIOps&#xff09;是一种使用人工智能应用程序来调节IT操作和维护的实践方式。它结合了大数据和机器学习技术&#xff0c;旨在自动化和改进IT操作和维护任务&#xff0c;如故障检测、因果分析和自动故障修复。以下是智能操作和维护的具体内容、挑战和解决方…

使用Node.js构建一个简单的聊天机器人

当谈到人工智能&#xff0c;我们往往会想到什么&#xff1f;是智能语音助手、自动回复机器人等。在前端开发领域中&#xff0c;我们也可以利用Node.js来构建一个简单而有趣的聊天机器人。本文将带你一步步实现一个基于Node.js的聊天机器人&#xff0c;并了解其工作原理。 首先…