java之多线程篇
一、基本概念
1.什么是线程?
线程就是,操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。简单理解就是:应用软件中互相独立,可以同时运行的功能
2.什么是多线程?
有了多线程,我们就可以让程序同时做多件事情
3.多线程的作用?
提高效率
4.线程的应用场景?
只要你想让多个事情同时运行就需要用到多线程
比如:软件中的耗时操作、所有的聊天软件、所有的服务器
二、并发和并行的概念
1.什么是并发?
并发就是,同一时刻,有多个指令在单个CPU上交替执行。
2.什么是并行?
并行就是,同一时刻,有多个指令在多个CPU上同时执行
3.电脑不是只有一个CPU么,这个多个CPU同时执行的并行究竟是什么?
其实,CPU在市面有很多类型如下
比如2核4线程的CPU,就可以同时运行4个线程的任务。
三、多线程的实现方式(3种)
1.继承Thread类的方式进行实现
用法:
1.定义一个类继承Thread类
2.这个类重写run方法
3.在main方法里面创建定义的类的对象
4.通过该对象的.start()方法启动线程
示例代码
public class ThreadDemo1 {public static void main(String[] args) {MyThread myThread1 = new MyThread();MyThread myThread2 = new MyThread();myThread1.setName("线程1");myThread2.setName("线程2");myThread1.start();myThread2.start();} } class MyThread extends Thread{@Overridepublic void run(){for(int i=0;i<100;i++){System.out.println(Thread.currentThread().getName()+" "+i);}} }
上面的两个线程的代码run方法是同时执行的,并不会等一个线程的循环走完。
注意:线程类开启后执行的是run方法的代码
2.实现Runnable接口的方式进行实现
用法:
1.自己定义一个类实现Runnable接口
2.重写里面的run方法
3.在main方法创建自己的类的对象
4.将定义的类传递给Thread构造方法创建一个Thread类的对象,并开启线程
示例
public class ThreadDemo2 {public static void main(String[] args) {MyThread myThread = new MyThread();Thread thread1 = new Thread(myThread);Thread thread2 = new Thread(myThread);thread1.setName("线程1");thread2.setName("线程2");thread2.start();thread1.start();} } class MyThread implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+"-->"+i);}} }
3.利用Callable接口和Future接口方式实现
前面两种实现方式,run方法没有返回值,不知道线程实现的结果,现在这个第三种方法是有返回值的。
用法:
1.创建一个类MyCallable实现callable接口
2.重写call(是有返回值的,表示多线程运行的结果)
3.创建MyCallable的对象(表示多线程要执行的任务)
4.传递MyCallable对象为参数创建FutureTask的对象(作用管理多线程运行的结果)
5.传递FutureTask对象为参数创建Thread类的对象,并启动(表示线程)
示例
public class ThreadDemo3 {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable mc = new MyCallable();FutureTask<Integer> ft = new FutureTask<>(mc);Thread t = new Thread(ft);t.start();System.out.println(ft.get());} } class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}return sum;} }
结果输出5050
4.三种方式的比较
四、Thread类的常用成员方法
1.七个方法
2.前四个细节
String getName()
void setName(String name)
细节:
1. 果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
2.如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
static Thread currentThread()
细节:
当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
在以前,我们写的所有的代码,其实都是运行在main线程当中
static void sleep(long time)
细节:
1.哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2.方法的参数:就表示睡眠的时间,单位毫秒
1秒 = 1000毫秒
3.时间到了之后,线程会自动的醒来,继续执行下面的其他代码
3.线程的优先级
(1)线程调度
抢占式调度:各条线程执行的顺序和时间是不确定的(随机)
非抢占式调度:各条线程执行的顺序是轮流执行和执行时间是差不多的
java中是选取第一种抢占式调度
(2)优先级(1~10)
抢占式调度就要涉及到线程的优先级越大,执行的顺序越前
于是可以通过Thread类的上面的两个成员方法来设置和获取线程的优先级
没有设置默认就是5
注意:这里的优先级是指优先级大的线程先执行的概率比较大,而不是百分百,比如说有两个一样的方法的线程,优先大的线程是有概率计较大的先执行完,但还是有小概率执行慢
4.守护线程
当一个线程使用Thread类的etDaemon(boolean on)时,这个线程变成守护线程。
细节:
这个线程也可以叫做“备胎”线程,它将其他线程视为“女神”线程,当其他线程结束的时候,这个守护线程就会陆续结束,觉得自己没必要存在了,这就会可能导致守护线程的代码没有全部执行完。
应用场景
上面聊天的场景,两个人聊天开两个线程,一个聊天,一个传输文件,如果聊天线程关闭了,就没有传输文件的必要了,于是将传输文件的线程设置为守护线程。
5.出让/礼让线程
在当前线程的工作不重要时,将CPU资源让位给其他线程,通过使用Thread类的
yield()
方法来将当前资源让位给其他同优先级线程public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println("线程1开始运行!");for (int i = 0; i < 50; i++) {if(i % 5 == 0) {System.out.println("让位!");Thread.yield();}System.out.println("1打印:"+i);}System.out.println("线程1结束!");});Thread t2 = new Thread(() -> {System.out.println("线程2开始运行!");for (int i = 0; i < 50; i++) {System.out.println("2打印:"+i);}});t1.start();t2.start(); }
观察结果,我们发现,在让位之后,尽可能多的在执行线程2的内容。
6.插入线程
当我们希望一个线程等待另一个线程执行完成后再继续进行,我们可以使用Thread类的
join()
方法来实现线程的插入。public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println("线程1开始运行!");for (int i = 0; i < 50; i++) {System.out.println("1打印:"+i);}System.out.println("线程1结束!");});Thread t2 = new Thread(() -> {System.out.println("线程2开始运行!");for (int i = 0; i < 50; i++) {System.out.println("2打印:"+i);if(i == 10){try {System.out.println("线程1加入到此线程!");t1.join(); //在i==10时,让线程1加入,先完成线程1的内容,再继续当前内容} catch (InterruptedException e) {e.printStackTrace();}}}});t1.start();t2.start(); }
线程2执行一半,线程1加入,先完成线程1的内容,再继续线程2的内容
五、线程的生命周期
六、线程的安全问题
1.售票代码引出问题
有100张票售卖,一共3个窗口在卖。
下面代码设置三个线程当作三个卖票的窗口,看看有什么问题
public class ThreadSafetyProblem {public static void main(String[] args) {MyThread1 myThread1 = new MyThread1();MyThread1 myThread2 = new MyThread1();MyThread1 myThread3 = new MyThread1();myThread1.setName("窗口1");myThread2.setName("窗口2");myThread3.setName("窗口3");myThread1.start();myThread2.start();myThread3.start();} } class MyThread1 extends Thread{//静态变量,几个线程共享private static int count = 0;public void run(){while(true){try {//这里只能try-catch丢给JVM,不能throwsThread.sleep(100); //因为父类的run方法没有throws} catch (InterruptedException e) {throw new RuntimeException(e);}if (count < 100){System.out.println(Thread.currentThread().getName() + "正在卖第" + ++count+"张票");}else{break;}}} }
2.超卖和重复卖问题
运行后出现的问题结果如下图:
三个窗口在同时卖一张票,不合法!
超卖了,仅有100张票
![]()
原因
线程的执行是有随机性的,cpu的执行权有可能被其他线程抢走。
线程1执行完票数的自增还没来得及打印的时候,线程2和线程3完成自增就会导致超卖和重复卖
那么要怎么解决这个安全问题呢?下一个点就会讲到解决的方法之一——同步代码块。
七、同步代码块
同步代码块的意思就是,把操作共享数据的代码锁起来
1.格式
synchronized(锁){
操作共享数据的代码
}
特点:
1.锁默认打开,有一个线程进去了,锁自动关闭
2.里面的代码全部执行完毕,线程出来,锁自动打开
2.修改后的售票代码
class MyThread1 extends Thread{//静态变量,几个线程共享private static int count = 0;//锁对象,一定要是唯一的static Object obj = new Object();public void run(){while(true){//同步代码块synchronized (obj){try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}if (count < 2000){System.out.println(Thread.currentThread().getName() + "正在卖第" + ++count+"张票");}else {break;}}}} }
运行后的结果,票正常售卖,从第一张卖到最后一张。
3.细节问题
细节1:同步代码块不能放在while循环里面,因为放在外的话,一个线程拿到锁后就会必须把循环执行完,才会释放锁,这样的话一个线程就把票都卖完了。
细节2:锁的对象必须是唯一的,修改后的代码的锁是一个Object对象,用static修饰后表示全局共享唯一的对象。也可以使用MyThread.class表示唯一的字节码文件对象
八、同步方法
如果我们要锁的代码是整个方法,这个时候就要用到同步方法了
就是把synchronized关键字加到方法上
1.格式
修饰符 synchronized 返回值类型 方法名(方法参数){….}
2.特点
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定,如果方法是非静态的,锁对象就是方法所在类对象this
如果方法是静态的,锁对象就是当前类的字节码文件对象
3.使用同步方法解决售票问题
示例代码
这段代码与前面不同的是,这段代码是使用实现Runnable接口实现的多线程,只需要创建一个实现Runnable接口的javabean类的对象,所以票数这个变量的内存地址是唯一的,所以不用像上面的代码一样用static修饰票数count
注意:下面同步方法是非静态的,所以锁对象就是MyRunnable类的对象mr
public class ThreadSafetyProblem {public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread myThread1 = new Thread(mr);Thread myThread2 = new Thread(mr);Thread myThread3 = new Thread(mr);myThread1.setName("窗口1");myThread2.setName("窗口2");myThread3.setName("窗口3");myThread1.start();myThread2.start();myThread3.start();}
}
class MyRunnable implements Runnable{int count = 0;public void run(){while(true){//同步代码块if (method()) break;}}//锁对象是thisprivate synchronized boolean method() {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}if (count < 2000){System.out.println(Thread.currentThread().getName() + "正在卖第" + ++count+"张票");}else {return true;}return false;}
}
4.StringBuffer为什么是线程安全的?
虽然StringBuilder和StringBuffer的成员方法是一样的,但为什么之前建议在多线程的情况下使用StringBuffer?
这是由于StringBuffer的成员方法比较 StringBuilder多了一个Sychronized修饰词,保证了线程的安全,但是在单线程的情况下,还是使用StringBuilder好一些。
九、Lock锁
1.售票代码使用手动上锁
public class ThreadSafetyProblem {public static void main(String[] args) {MyThread1 myThread1 = new MyThread1();MyThread1 myThread2 = new MyThread1();MyThread1 myThread3 = new MyThread1();myThread1.setName("窗口1");myThread2.setName("窗口2");myThread3.setName("窗口3");myThread1.start();myThread2.start();myThread3.start();}
}
class MyThread1 extends Thread{//静态变量,几个线程共享private static int count = 0;//创建一个锁对象 用static修饰表示共享唯一Lock lock = new ReentrantLock();public void run(){while(true){lock.lock();try {Thread.sleep(10);if (count < 2000){System.out.println(Thread.currentThread().getName() + "正在卖第" + ++count+"张票");}else {break;}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}
十、死锁
1.示例代码
下面的代码用了锁的嵌套
public class MyThread extends Thread {static Object objA = new Object();static Object objB = new Object();@Overridepublic void run() {//1.循环while (true) {if ("线程1".equals(getName())) {synchronized (objA) {System.out.println("线程1拿到了A锁,准备拿B锁");//线程1卡在这里synchronized (objB) {System.out.println("线程1拿到了B锁,顺利执行完一轮");}}} else if ("线程2".equals(getName())) {if ("线程2".equals(getName())) {synchronized (objB) {System.out.println("线程2拿到了B锁,准备拿A锁");//线程2卡在这里synchronized (objA) {System.out.println("线程2拿到了A锁,顺利执行完一轮");}}}}}} }
运行结果
显然两个线程都卡在第一层同步代码的锁那里,程序结束不了。
2.如何解决
很简单,不要写锁或同步代码块的锁嵌套就行。
十一、生产者和消费者模式
生产者消费者模式是一个十分经典的多线程协作的模式,就是要打破两个线程随机执行的规则,你一次我一次。
1.流程框图(等待唤醒机制)
2.涉及的方法
调用
wait()
方法的线程会释放它持有的锁,并进入等待状态,直到它被其他线程通过调用notify()
或notifyAll()
唤醒。
3.步骤
消费者和生产者的代码都按下面的步骤走:
1.循环
2.同步代码块(给消费者或生产者上锁)
3.判断共享数据是否到了末尾(到了末尾)
4.判断共享数据是否到了末尾(没有到末尾,执行(消费者或生产者的)核心逻辑)
4. 示例代码
(1)桌子代码
public class Desk {/*桌子的作用:控制生产者和消费者的执行*/// 判断桌子上有没有食物// 0 没有食物 生产者的线程执行// 1 有食物 消费者的线程执行//这里一般不用boolean类型,因为boolean类型只能是true或者false//如果有多条线程,int 可以表示多条线程的状态public static int foodFlat = 0;// 消费者现在所能吃食物的碗数public static int count = 10;//锁对象static Object lock = new Object();
}
(2)消费者代码
public class Foodie extends Thread {/*1.循环2.同步代码块(给消费者或生产者上锁)3.判断共享数据是否到了末尾(到了末尾)4.判断共享数据是否到了末尾(没有到末尾,执行(消费者或生产者的)核心逻辑)*/public void run(){while(true){synchronized (Desk.lock){if (Desk.count == 0){//吃不下了,直接结束break;}else {//如果桌上没有食物if (Desk.foodFlat == 0){try {//进入等待Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {//桌上有食物Desk.foodFlat = 0;Desk.count--;System.out.println("消费者吃掉了一碗面条,还能吃"+Desk.count+"碗");//通知唤醒厨师Desk.lock.notify();}}}}}
}
(3)生产者代码
public class Cook extends Thread {public void run(){while(true){synchronized (Desk.lock){//判断消费者是否吃饱了if (Desk.count==0){//吃饱了就结束break;}else {//消费者还能吃//判断桌子上还有食物么//有食物就进入等待if (Desk.foodFlat==1){try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {//没食物//厨师做一碗面条Desk.foodFlat = 1;System.out.println("厨师做了一碗面条");//做好就唤醒消费者Desk.lock.notify();}}}}}}
(4)main方法
public class Test {public static void main(String[] args) {Desk desk = new Desk();Foodie foodie = new Foodie();Cook cook = new Cook();foodie.setName("吃货");cook.setName("厨师");foodie.start();cook.start();}
}
(5)执行结果
从运行结果可以看出来,消费者和生产者模式可以使多个线程不再随机而是按顺序的来执行。
5.阻塞队列实现唤醒机制
(1)成员方法put和take
put方法底层原理
放入一个数据,put方法接收数据,先使当前线程获得锁(这个队列的),唤醒等待的线程,如果队列满了,当前线程进入等待,释放当前锁。
take方法底层原理
take方法取出数据,先使当前线程获得锁(这个队列的),唤醒等待的线程,如果队列空的,当前线程进入等待,释放当前锁。
(2)代码实现
用阻塞队列实现消费者和生产者的示例代码
消费者代码
public class Foodie extends Thread {ArrayBlockingQueue<String> queue;public Foodie(ArrayBlockingQueue<String> queue){this.queue = queue;}public void run() {while(true){try {queue.take();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("吃货吃了一碗面条");}} }
生产者代码
public class Cook extends Thread{ArrayBlockingQueue<String> queue;public Cook(ArrayBlockingQueue<String> queue) {this.queue = queue;}public void run() {while(true){try {queue.put("一碗面");} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("厨师做了一碗面条");}} }
main方法
public class Test {public static void main(String[] args) {ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);Foodie f = new Foodie(queue);Cook c = new Cook(queue);f.setName("吃货");c.setName("厨师");f.start();c.start();} }
运行结果
![]()
看到上面的结果有人会认为,厨师连续做了两碗面,是不是违反了模式了?
其实不然,消费者和生产者两条线程还是一条一次轮流执行的,重复输出是因为输出的代码放在了锁的外面,所以两个线程是随机的,抢着输出的,厨师做了多少碗面和消费者吃了多少碗的数量还是一样的。厨师放一碗面到队列,吃货就拿一碗。
注意:锁只在阻塞队列里面,即示例代码的put和take方法里面
十二、线程的6种状态
严格的来说,线程有7种状态,但是线程在进入要运行阶段的时候,JVM直接将线程丢给操作系统,java就不管了。所以对于java来说,线程是有6种状态。如下,
十三、Demo
1.Demo1
有100份礼品,两人同时发送,当剩下的礼品品小于10份分的时候则不再送出
利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
public class MyRunnable implements Runnable{int count = 100;@Overridepublic void run() {while (true) {synchronized (this) {if (count < 10){break;}else {count--;System.out.println(Thread.currentThread().getName() + "送出一份礼物,剩余" + count + "份礼物");}}}} } public class Test {public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.setName("小明");t2.setName("小红");t1.start();t2.start();} }
2.Demo2
![]()
public class MyRunnable implements Runnable{double money = 100;int count = 3;Random rnd = new Random();public void run(){synchronized (this) {if (count == 0){System.out.println(Thread.currentThread().getName()+"没抢到");}else if (count == 1){count--;System.out.println(Thread.currentThread().getName()+"抢到了"+money+"元");money = 0;}else {double get = (rnd.nextDouble()*money);System.out.println(Thread.currentThread().getName()+"抢到了"+get+"元");money = money - get;count--;}}} } public class Test {public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);Thread t3 = new Thread(mr);Thread t4 = new Thread(mr);Thread t5 = new Thread(mr);t1.setName("玩家1");t2.setName("玩家2");t3.setName("玩家3");t4.setName("玩家4");t5.setName("玩家5");t1.start();t2.start();t3.start();t4.start();t5.start();} }
3.Demo3
public class MyRunnable implements Runnable{//奖池ArrayList<Integer> list;public MyRunnable(ArrayList<Integer> list) {this.list = list;}@Overridepublic void run() {while (true){synchronized (this) {if (list.isEmpty()){break;}Collections.shuffle(list);System.out.println(Thread.currentThread().getName()+"又产生了一个"+list.get(0)+"元大奖");list.remove(0);}//在锁外面休眠一会,这样另外一个线程就先执行,输出会好看一点try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}} } public class Test {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);MyRunnable mr = new MyRunnable(list);Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.setName("抽奖箱1");t2.setName("抽奖箱2");t1.start();t2.start();} }
4.Demo4 (多线程统计并求最大值)
public class MyRunnable implements Runnable{//奖池ArrayList<Integer> list;public MyRunnable(ArrayList<Integer> list) {this.list = list;}@Overridepublic void run() {//定义一个集合,收集每一个抽奖箱每次中奖的金额ArrayList<Integer> moneys = new ArrayList<>();while (true){synchronized (this) {if (list.isEmpty()){Collections.sort(moneys);System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName()+"总共产生了"+moneys.size()+"个奖项");String string = moneys.toString();System.out.println("\t分别为:"+string.substring(1,string.length()-1)+"最高奖项为"+moneys.get(moneys.size() - 1)+",总计额为"+moneys.stream().mapToInt(Integer::intValue).sum());break;}Collections.shuffle(list);moneys.add(list.get(0));list.remove(0);}try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}} } public class Test {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);String name1 = "抽奖箱1";String name2 = "抽奖箱2";MyRunnable mr = new MyRunnable(list);Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.setName(name1);t2.setName(name2);t1.start();t2.start();} }
5.Demo5(多线程之间的比较)
由于这里要多线程之间进行比较,所以必须要有返回值,run方法没有返回值,所以使用第三种方法实现多线程,即实现Callable接口和Future接口
public class MyCallable implements Callable<Integer> {//奖池ArrayList<Integer> list;public MyCallable(ArrayList<Integer> list) {this.list = list;}@Overridepublic Integer call(){//定义一个集合,收集每一个抽奖箱每次中奖的金额ArrayList<Integer> moneys = new ArrayList<>();while (true){synchronized (this) {if (list.isEmpty()){Collections.sort(moneys);System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName()+"总共产生了"+moneys.size()+"个奖项");String string = moneys.toString();System.out.println("\t分别为:"+string.substring(1,string.length()-1)+"最高奖项为"+moneys.get(moneys.size() - 1)+",总计额为"+moneys.stream().mapToInt(Integer::intValue).sum());break;}Collections.shuffle(list);moneys.add(list.get(0));list.remove(0);}try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}return moneys.get(moneys.size() - 1);} } public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {ArrayList<Integer> list = new ArrayList<>();Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);String name1 = "抽奖箱1";String name2 = "抽奖箱2";MyCallable mc = new MyCallable(list);FutureTask<Integer> ft1 = new FutureTask<>(mc);FutureTask<Integer> ft2 = new FutureTask<>(mc);Thread t1 = new Thread(ft1,name1);Thread t2 = new Thread(ft2,name2);t1.start();t2.start();if (ft1.get()>ft2.get()){System.out.println("在此次抽奖过程中,"+name1+"中产生了最大奖项,该奖项金额为"+ft1.get()+"元");}else {System.out.println("在此次抽奖过程中,"+name2+"中产生了最大奖项,该奖项金额为"+ft2.get()+"元");}} }
十四、多线程的内存图
十五、线程池
1.核心原理
(1)创建一个池子,池子中是空的
(2)提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子
下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
(3)但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
2.操作步骤
(1)创建线程池对象
(2)提交任务(提交线程)
(3)任务都执行完毕后,关闭线程池
注意:一般的服务器线程池是不会关闭的,比如王者游戏24小时都能玩
3.自定义线程池
把一个餐厅的运营的七大核心因素看作线程的参数
(1)两种情况+三个临界点
A.线程池的参数如下,提交的任务数小于3+3+3,因此不会触发任务过多解决方案,8个任务从核心线程开始放,然后放队伍,发现不够放了,于是找来临时工(临时线程)放多余的两个任务。
注意:任务并不是先放就先执行,比如下面任务7,8后放比在排队的4,5,6先走。
B.下面这种情况和上面不同的是,提交的任务数量超过了3+3+3,于是最后一个任务触发了任务拒绝策略。 其他和上面相同
三个临界点
(2)任务拒绝策略
(3)创建一个线程池
构造方法和参数如下
使用这个线程池就提交任务就行pool.submit(线程任务)
(4)最大并行数
在多线程编程中,指同时运行的线程数量的上限。
下面代码可以获得java可用的处理器的数目,即可同时运行线程的最大数量
(5)线程池多大合适
![]()
cpu计算时间和等待时间可以通过插件thread dump计算统计
相关文章:

java之多线程篇
一、基本概念 1.什么是线程? 线程就是,操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。简单理解就是:应用软件中互相独立,可以同时运行的功能 2.什么是多线程? 有了多线…...
【深度学习】TTS,CosyVoice,训练脚本解析
https://github.com/FunAudioLLM/CosyVoice/blob/main/examples/libritts/cosyvoice/run.sh Bash 脚本是一个语音合成(TTS)训练和推理的完整流水线。让我们逐步解析这个脚本的各个部分。 初始化部分 #!/bin/bash # Copyright 2024 Alibaba Inc. All Rights Reserved. . ./…...

《Unity3D网络游戏实战》学习与实践
纸上得来终觉浅,绝知此事要躬行~ Echo 网络上的两个程序通过一个双向的通信连接实现数据交换,这个连接的一端称为一个Socket “端口”是英文port的意译,是设备与外界通信交流的出口。每台计算机可以分配0到65535共65536个端口 每一条Sock…...

Machine_Matrix打靶渗透【附代码】(权限提升)
靶机下载地址: https%3A%2F%2Fdownload.vulnhub.com%2Fmatrix%2FMachine_Matrix.zip 1. 主机发现端口扫描目录扫描敏感信息获取 1.1. 主机发现 nmap -sn 192.168.7.0/24|grep -B 2 08:00:27:D9:36:81 1.2. 端口扫描 nmap -p- 192.168.7.155 1.3. 目录扫描 dir…...

代码随想录算法训练营Day22 | Leetcode 77 组合 Leetcode 216 组合总和Ⅲ Leetcode17 电话号码的字母组合
前言 回溯算法中递归的逻辑不重要,只要掌握回溯的模板以及将问题转化为树形图,整个问题就很好解决了,比二叉树简单。 Leetcode 77 组合 题目链接:77. 组合 - 力扣(LeetCode) 代码随想录题解:…...

【微信小程序实战教程】之微信小程序中的 JavaScript
微信小程序中的 JavaScript 微信小程序的业务逻辑都是通过JavaScript语言来实现的,本章我们将详细的讲解JavaScript的基本概念,以及在小程序中如何使用JavaScript语言。JavaScript是一种轻量的、解释型的、面向对象的头等函数语言,是一种动态…...
K-近邻算法(一)
一、 K- 近邻算法 (KNN) 概念 1.1 K- 近邻算法 (KNN) 概念 K Nearest Neighbor 算法⼜叫 KNN 算法,这个算法是机器学习⾥⾯⼀个⽐较经典的算法, 总体来说 KNN 算法是相对⽐ 较容易理解的算法 定义 : 如果⼀个样本在特征空间中的k 个最相似 ( 即特征空间…...

从零开始之AI视频制作篇
从零开始之AI视频制作篇 文章目录 从零开始之AI视频制作篇前言一、工具列表二、成片展示三、制作流程1、获取图片素材2、图片生成视频2.1 Runway操作流程 3、文本生成语音3.1 Fish Audio操作流程 4、视频剪辑4.1 音频素材4.2 字幕生成 四、Runway提示词参考:参考 前…...
Java之TCP编程综合案例
1.反转案例 搭建一个TCP客户端,从键盘录入整行数据(遇到quit结束录入)然后发送给服务器,再接收服务器返回的数据并输出。 package com.briup.chap12;public class Test064_ReversalClient {public static void main(String[] ar…...

【数据分析---Pandas实战指南:精通数据查询、增删改操作与高效索引和列名操作管理】
前言: 💞💞大家好,我是书生♡,本阶段和大家一起分享和探索数据分析,本篇文章主要讲述了:数据查询操作,数据增删改操作,索引和列名操作等等。欢迎大家一起探索讨论&#x…...
Spring Cloud全解析:注册中心之Eureka服务获取和服务续约
服务获取和服务续约 eureka客户端通过定时任务的方式进行服务获取和服务续约,在com.netflix.discovery.DiscoveryClient类中,启动了两个定时任务来进行处理 private void initScheduledTasks() {// 是否需要拉取if (clientConfig.shouldFetchRegistry(…...

三相整流电路交流侧谐波仿真分析及计算
一、三相桥式全控整流电路和功率因数测量电路SIMULINK 模型 如图4-1,根据高频焊机的主电路机构和工作原理,可将高频焊机三相整流部分等效为阻感负载的三相桥式全控整流电路模型,其由三相交流电压源、三相晶闸管整流桥、同步六脉冲触发器和阻感…...
了解Java中的反射,带你如何使用反射
反射的定义 反射(Reflection)是Java的一种强大机制,它允许程序在运行时动态地查询和操作类的属性和方法。通过反射,Java程序可以获取类的信息,比如类的名称、方法、字段,以及可以动态地创建对象、调用方法…...

【c++】基础知识——快速入门c++
🌟🌟作者主页:ephemerals__ 🌟🌟所属专栏:C 目录 前言 一、手搓一个Hello World 二、命名空间namespace 1.命名空间的定义 2.命名空间的使用 3.命名空间补充知识 三、c中的输入和输出 四、缺省参…...

AI学习记录 - 自注意力机制的计算流程图
过段时间解释一下,为啥这样子计算,研究这个自注意力花了不少时间,网上很多讲概念,但是没有具体的流程图和计算方式总结…...
JavaScript快速入门,满满干货总结,快速掌握JS语法,DOM,BOM,事件
目录 一. JavaScript、HTML、CSS简介 1.1 HTML简介和举例说明 1.2 CSS简介和举例说明 1.3 JavaScript 简介和举例说明 二. JavaScript 基本语法 2.1 变量类型和定义方式 2.2 逻辑运算符,比较运算符 2.3 流程控制,if,if...else...&…...

【C++】C++入门基础【类与对象】
目录 1.类 1.1类的定义 1.2struct 与 class对比 2.访问限定符 3. 类域 4.实例化 5.存储大小----内存对齐 6.this指针 1.类 1.1类的定义 class作为类的关键字,后面跟的是类的名字,如Stack,{}中的为类的主体,类定义结束时…...
Qt | QScatterSeries 散点图
点击上方"蓝字"关注我们 01、QScatterSeries QScatterSeries 的类,它将代表散点图中的一个系列。这个类将包含数据点、颜色和样式等属性,以及用于绘制散点图的方法。 02、main.cpp #include <QtWidgets/QApplication>#include <QtWidgets/QMainWindow…...

无缝协作的艺术:Codigger 视频会议(Meeting)的用户体验
在当今数字化的时代,远程协作已经成为工作和学习中不可或缺的一部分。然而,远程协作也面临着诸多挑战,如沟通不畅、信息同步不及时、协作工具的复杂性等。而 Codigger 视频会议(Meeting)作为一款创新的工具,…...

C基础练习(学生管理系统)
1.系统运行,打开如下界面。列出系统帮助菜单(即命令菜单),提示输入命令 2.开始时还没有录入成绩,所以输入命令 L 也无法列出成绩。应提示“成绩表为空!请先使用命令 T 录入学生成绩。” 同理,当…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...