25-多线程

多线程

线程(Thread)是一个程序内部的一条执行流程。
程序中如果有一条执行流程,那这个程序就是单线程的程序
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
再例如:消息通信、淘宝、京东系统都离不开多线程技术

如何在程序中创建出多条线程?

Java是通过java.lang.Thread 类的对象来代表线程的。
有两种方法可以创建新的执行线程。 一种是将类声明为Thread的子类。 此子类应覆盖类Threadrun方法。 然后可以分配和启动子类的实例。
run方法是我们这个线程做什么事情

     class PrimeThread extends Thread {long minPrime;PrimeThread(long minPrime) {this.minPrime = minPrime;}public void run() {// compute primes larger than minPrime. . .}}PrimeThread p = new PrimeThread(143);p.start();

多线程的创建方式一:继承Thread类

定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
创建MyThread类的对象
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
方式一优缺点:
优点:编码简单
缺点:线程类已经继承 Thread,无法继承其他类,不利于功能的扩展。(java 可以嵌套基层,但不能同时继承多个类,一个类只能有一个父类)

// 子类的线程
threadClass tc = new threadClass();
tc.start();
// 主函数的线程
for (int i = 0; i < 5; i++) {System.out.println("main thread is " + i);
}public class Mythead extends Thread {@Overridepublic void run(){for (int i = 0; i <5 ; i++) {System.out.println("my thread is "+ i);}}
}

main本身也是一个线程
线程是同时执行的,所以这个执行语句输出可能是随机的, 并不是俺早顺序输出的。

my thread is 0
my thread is 1
main thread is 0
main thread is 1
main thread is 2
main thread is 3
main thread is 4
my thread is 2
my thread is 3
my thread is 4
注意事项

1、启动线程必须是调用start方法,不是调用run方法。
直接调用 run 方法会当成普通方法执行(当成一个类中的内部方法来执行语句),此时相当于还是单线程执行。
只有调用start方法才是启动一个新的线程执行。
2、不要把主线程任务放在启动子线程之前。
这样主线程一直是先跑完的,相当于是一个单线程的效果了。

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

  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  2. 创建MyRunnable任务对象
  3. 把MyRunnable任务对象交给Thread处理。
  4. 调用线程对象的start()方法启动线程
Thread类提供的构造器说明
public Thread(Runnable target)封装Runnable对象成为线程对象

方式二的优缺点

优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象。

举例代码

public static void main(String[] args) {RunnableObject ro = new RunnableObject(123);new Thread(ro).start();for (int i = 0; i <7 ; i++) {System.out.println("主 thread is "+ i);}
}//这个类和别的类几乎没什么区别,我们也可以正常的定义成员变量什么的
public class RunnableObject implements Runnable{long minRunObj;public RunnableObject() {}public RunnableObject(long minRunObj) {this.minRunObj = minRunObj;}@Overridepublic void run() {for (int i = 0; i <7 ; i++) {System.out.println("我的 thread is "+ i);}}
}
创建方式二的匿名内部类的方式写法

如果这个类我们只使用一次的话, 我们就可以使用这个匿名内部类, 匿名内部类是实现这个接口和实现这个抽象类的一种方法, 我们可以直接在这个函数传递的参数中用匿名内部类来代替实际的参数, 比如下面的这种, 这个 new Runnable 本质上就是一个接口. 我们实现这个接口就是实现了这个匿名内部类.

new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i <7 ; i++) {System.out.println("我的 thread is "+ i);}}
}).start();

创建方式三实现Callable接口

前面两种创建方式都存在的一个问题
假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。
JDK 5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)。
这种方式最大的优点:可以返回线程执行完毕后的结果。

  1. 创建任务对象
    • 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
    • 把Callable类型的对象封装成FutureTask(线程任务对象)
  2. 把线程任务对象交给Thread对象。
  3. 调用Thread对象的start方法启动线程。
  4. 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
    FutureTask的API
FutureTask提供的构造器说明
public FutureTask<>(Callable call)把Callable对象封装成FutureTask对象。
FutureTask提供的方法说明
public V get() throws Exception获取线程执行call方法返回的结果。

线程创建方式三的优缺点
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。

举例
    public static void main(String[] args) throws ExecutionException, InterruptedException {//创建对象callableObject co = new callableObject(100);//是一个任务对象,实现了这个runnable接口,可以直接使用FutureTask<String> sft = new FutureTask<>(co);new Thread(sft).start();System.out.println(sft.get());  //这个获取结果的顺序必须要在启动线程之后,否则没有结果}

实现callable接口的对象

public class callableObject implements Callable<String> {private int n;public callableObject(int n) {this.n = n;}@Overridepublic String call() throws Exception {//求1-n的和int sum=0;for (int i = 0; i < n; i++) {sum+=i;}return "线程求出了1-"+ n+"的和是"+sum;}
}

需要注意的是这个 callable 是一个泛型的变量, 我们在使用的时候要根据返回值对其确定类型如果我们的 call 函数的返回值是一个 String 类型的变量, 那么我们就要在这个 callable 后面加上这个<String>否则的话我们这个 call 函数的返回值就要设置为object

三种线程的创建方式的不同点

方式优点缺点
继承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()…让调用当前这个方法的线程先执行完!

其中比较重点的是getName,和setName,以及这个currentThread,sleep,join等方法
尤其是这个currentThread很重要
setName
我们可以通过这个代码来设置名字,我们也可以通过这个构造器中的代码来进行构造名字

        public Mythead(String name) {super(name);}

我们可以通过这个代码来获取当前执行的线程对象,使我们能够在随机执行的线程中找到当前线程对象

        Thread t3 = Thread.currentThread();System.out.println(t3.getName());//System.out.println(Thread.currentThread().getName());

sleep 这个可以让这个程序变快,变慢(可以通过这个设置会员和非会员的速度, 会员的话就让其不休眠, 非会员的话就让它每次运行的话休眠 0.3 秒, 这样不太影响体验, 但确实有区别, 哈哈)
Thread.sleep(5000)
让这个当前线程休眠5秒
join 方法
注意:
main函数中使用Thread.currentThread().join()会导致主线程(即main线程)等待自己,这会导致程序陷入死锁状态,因为主线程无法等待自己完成。

        Thread t1= new Mythead();t1.start();t1.join();System.out.println(t1.getName());

常见构造器

Thread提供的常见构造器说明
public Thread(String name)可以为当前线程指定名称
public Thread(Runnable target)封装Runnable对象成为线程对象
public Thread(Runnable target, String name)封装Runnable对象成为线程对象,并指定线程名称

线程安全问题

多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
线程安全问题发生的原因是什么?
多个线程,同时访问同一个共享资源,且同时修改该资源。就会出现问题.

取钱的线程安全问题

场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?
取钱的三步
1、判断余额是否足够
2、吐出100000元
3、更新账户余额
第一步两个人执行的时候因为多线程不是按照顺序来的,因为我们可能会两个人通过对余额判断成功

image-20230810112226615

线程安全问题出现的原因?

  1. 存在多个线程在同时执行
  2. 同时访问一个共享资源
  3. 存在修改该共享资源

用程序模拟线程安全问题

主函数

    public static void main(String[] args) throws ExecutionException, InterruptedException {account ac = new account(100000);myThread mTh = new myThread(ac,90000,"小明");FutureTask ft = new FutureTask(mTh);myThread mTh2 = new myThread(ac,50000,"小红");FutureTask ft2 = new FutureTask(mTh2);new Thread(ft).start();new Thread(ft2).start();}

    @Overridepublic Double call() throws Exception {// 判断这个钱是不是还够,如果不够提示,如果够的话,修改余额if (ac.getBalance() < balance) {// 钱不够了System.out.println(name + "取钱的时候,账户余额不足");} else {// 钱还够System.out.println(name + "取钱的时候,账户余额充足");ac.setBalance(ac.getBalance() - balance);System.out.println(name + "取钱成功,账户余额为" + ac.getBalance());}return 0.0;}

出现了这个线程冲突

小明在取钱,余额充足,还有100000.0
小红在取钱,余额充足,还有100000.0
小红取完钱后,剩余-40000.0
小明取完钱后,剩余10000.0

线程同步 解决线程安全的问题

线程同步
解决线程安全问题的方案。
线程同步的思想
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的原理
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
加锁:让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

加锁的几种方法

同步代码块

作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

synchronized(同步锁) {访问共享资源的核心代码
}

原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
同步锁的注意事项
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出 bug。 下面的这种

    public static void main(String[] args) {account ac = new account(100000);myThread mTh = new myThread(ac,100000,"小明");FutureTask ft = new FutureTask(mTh);myThread mTh2 = new myThread(ac,100000,"小红");FutureTask ft2 = new FutureTask(mTh2);new Thread(ft).start();new Thread(ft2).start();}
    @Overridepublic Double call() throws Exception {synchronized ("黑马") {if (Double.compare(ac.getMoney(),getMoney)<0){System.out.println(this.name+"在取钱,余额不够,只剩"+ac.getMoney());}else {System.out.println(name+"在取钱,余额充足,还有"+ac.getMoney());ac.setMoney(ac.getMoney()-getMoney);System.out.println(name+"取完钱后,剩余"+ac.getMoney());}//返回余额return 0.0;}}

因为这个锁"黑马"是固定的,我们不能够用这个唯一的字符串作为锁,因为黑马不仅能够锁着小明和小红,还会锁着别的人
最好的是用小明和小红有,别人也有但是每一个张卡都不一样
synchronized (this.getclass())
静态方法由于是以类为单位来进行访问的,因此我们在使用的时候我们可以通过synchronized (Account.class);来上锁
锁对象随便选择一个唯一的对象好不好呢?
不好,会影响其他无关线程的执行。
锁对象的使用规范
建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。

同步方法

作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

修饰符 synchronized 返回值类型 方法名称(形参列表) {操作共享资源的代码
}

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
同步方法底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。
  • 如果方法是静态方法:同步方法默认用类名. Class 作为的锁对象。
    @Overridepublic synchronized Double call() throws Exception {if (Double.compare(ac.getMoney(),getMoney)<0){System.out.println(this.name+"在取钱,余额不够,只剩"+ac.getMoney());}else {System.out.println(name+"在取钱,余额充足,还有"+ac.getMoney());ac.setMoney(ac.getMoney()-getMoney);System.out.println(name+"取完钱后,剩余"+ac.getMoney());}//返回余额return 0.0;}

是同步代码块好还是同步方法好一点?
范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
可读性:同步方法更好。

方式三 lock锁

Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

构造器说明
public ReentrantLock()获得Lock锁的实现类对象

Lock的常用方法

方法名称说明
void lock()获得锁
void unlock()释放锁
  • Lock实现提供了比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构,可能具有完全不同的属性,并且支持多个关联的[Condition]对象。

注意点
为了防止这个锁解不开,我们一定要进行try finally进行解锁
手动进行加锁和解锁,lock,unlock
final我们最好用这个final进行修饰这个lock,防止它被替换

image-20230810122441126

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Account {private String cardId; // 卡号private double money; // 余额。// 创建了一个锁对象private final Lock lk = new ReentrantLock();public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}// 小明 小红线程同时过来的public void drawMoney(double money) {// 先搞清楚是谁来取钱?String name = Thread.currentThread().getName();try {lk.lock(); // 加锁// 1、判断余额是否足够if(this.money >= money){System.out.println(name + "来取钱" + money + "成功!");this.money -= money;System.out.println(name + "来取钱后,余额剩余:" + this.money);}else {System.out.println(name + "来取钱:余额不足~");}} catch (Exception e) {e.printStackTrace();} finally {lk.unlock(); // 解锁}}}

线程通信,了解一下,理解思想就好了

什么是线程通信?
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
线程通信的常见模型(生产者与消费者模型)
生产者线程负责生产数据
消费者线程负责消费生产者生产的数据。
注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!

image-20230810123021243

方法名称说明
void wait()让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程

注意
上述方法应该使用当前同步锁对象进行调用。
唤醒放在前面,等待放在后面
对下面的代码进行分析发现主函数中有五个线程,这五个线程是在不断的进行竞争的,不一定会谁先进行执行
所以我们必须要考虑到所有的情况,如果吃货执行的时候没有包子,那么就让其等待,唤醒别的线程,然后又有可能会是吃货执行,在来一次循环, 如果是厨师执行, 那么先判断这个有没有包子, 如果有的话就不做了, 继续等待, 唤醒别的线程, 如果没有的话就做, 做完之后唤醒别的, 把自己等待下去.
总会有一次是厨师线程执行的,因为这个执行是随机的,也不可能每次都不执行啊

public static void main(String[] args) {//   需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上//      2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。Desk desk  = new Desk();// 创建3个生产者线程(3个厨师)匿名内部类new Thread(() -> {while (true) {desk.put();}}, "厨师1").start();new Thread(() -> {while (true) {desk.put();}}, "厨师2").start();new Thread(() -> {while (true) {desk.put();}}, "厨师3").start();// 创建2个消费者线程(2个吃货)new Thread(() -> {while (true) {desk.get();}}, "吃货1").start();new Thread(() -> {while (true) {desk.get();}}, "吃货2").start();}
public class Desk {private List<String> list = new ArrayList<>();// 放1个包子的方法// 厨师1 厨师2 厨师3public synchronized void put() {try {String name = Thread.currentThread().getName();// 判断是否有包子。if(list.size() == 0){list.add(name + "做的肉包子");System.out.println(name + "做了一个肉包子~~");Thread.sleep(2000);// 唤醒别人, 等待自己this.notifyAll();this.wait();}else {// 有包子了,不做了。// 唤醒别人, 等待自己this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}// 吃货1 吃货2public synchronized void get() {try {String name = Thread.currentThread().getName();if(list.size() == 1){// 有包子,吃了System.out.println(name  + "吃了:" + list.get(0));list.clear();Thread.sleep(1000);this.notifyAll();this.wait();}else {// 没有包子this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}
}

线程池

线程池就是一个可以复用线程的技术。
不使用线程池的问题
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的, 而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。

image-20230810132351186

如何创建线程池

谁代表线程池?
JDK 5.0起提供了代表线程池的接口:ExecutorService。
如何得到线程池对象?
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

ThreadPoolExecutor构造器

一个函数七个参数类型

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler) 

参数一:corePoolSize : 指定线程池的核心线程的数量。
参数二:maximumPoolSize:指定线程池的最大线程数量。
参数三:keepAliveTime :指定临时线程的存活时间。
参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)
参数五:workQueue:指定线程池的任务队列。
参数六:threadFactory:指定线程池的线程工厂。
参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)

image-20230810154009891
例如

//new LinkedBlockingDeque<>() 无限大小//new ArrayBlockingQueue<>(4) 限制这个集合的大小new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

1、临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
2、什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

常用方法

方法名称说明
void execute(Runnable command)执行 Runnable 任务
Future<T> submit(Callable<T> task)执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果
void shutdown()等全部任务执行完毕后,再关闭线程池!
List<Runnable> shutdownNow()立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务

举例说明

main 函数

ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS, new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());Runnable target = new MyRunnable();pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(target);  //复用前面的核心线程,因为前面三个线程已经执行过了,又空出来了pool.execute(target);  //复用前面的核心线程pool.shutdown();

线程代码

实现的runable接口@Overridepublic void run() {String name = Thread.currentThread().getName();System.out.println(name + " is running");}

运行结果

pool-1-thread-2 is running
pool-1-thread-1 is running
pool-1-thread-3 is running
pool-1-thread-1 is running
pool-1-thread-2 is running
pool-1-thread-3 is runnin
进程已结束,退出代码0

注意shutdownNow()
shutdownNow()是不管这个线程池怎么样,只要执行到这里就关闭线程池
shutdown是等到这个线程任务执行完毕之后才关闭
实际上我们也不会关闭这个线程池

临时线程和拒绝任务

        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());Runnable target = new MyRunnable();pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(target);  //排队的有四个pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);  //创建临时线程  最多有两个pool.execute(target);  //创建临时线程pool.execute(target);  //拒绝任务

核心线程三个,排队的四个,然后临时线程两个,之后在添加就会拒绝任务了
此时就会抛出异常,提示你这个线程池满了

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task thread.pool.MyRunnable@4f3f5b24 rejected from java.util.concurrent.ThreadPoolExecutor@15aeb7ab[Running, pool size = 5, active threads = 5, queued tasks = 4, 

拒绝异常

策略详解
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常 这是不推荐的做法,你都不知道你错了没有,不推荐
ThreadPoolExecutor.DiscardOldestPolicy;抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法从而绕过线程池直接执行,由老板亲自处理新任务

线程池处理callable的处理方法

方法名称说明
void execute(Runnable command)执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future<T> submit(Callable<T> task)执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown()等任务执行完毕后关闭线程池
List<Runnable> shutdownNow()立刻关闭,停止正在执行的任务,并返回队列中未执行的任务
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());Future<String> submit1 = pool.submit(new MyCallable(100));Future<String> submit2 = pool.submit(new MyCallable(200));Future<String> submit3 = pool.submit(new MyCallable(300));System.out.println(submit1.get());System.out.println(submit2.get());System.out.println(submit3.get());Future<String> submit4 = pool.submit(new MyCallable(400));Future<String> submit5 = pool.submit(new MyCallable(500));System.out.println(submit4.get());System.out.println(submit5.get());
pool-1-thread-1执行从1-100的运算结果是10100
pool-1-thread-2执行从1-200的运算结果是40200
pool-1-thread-3执行从1-300的运算结果是90300
pool-1-thread-1执行从1-400的运算结果是160400
pool-1-thread-3执行从1-500的运算结果是250500

Executors 工具实现线程池

方法名称说明
public static ExecutorService newFixedThreadPool(int nThreads)创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor()创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newCachedThreadPool()线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

Executors使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
这种Executors创建的会导致这个等待的任务可以无限长

image-20230810162939695

核心线程数量到底配置成多少呢
计算密集型的任务,核心线程数量 = CPU的核数 + 1
IO密集型的任务: 核心线程数量 = CPU 核数 + 2

进程

  1. 正在运行的程序就是一个独立的进程
  2. 线程是属于进程的,一个进程中可以同时运行很多个线程。
  3. 进程中的多个线程其实是并发和并行执行的。

进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
cpu中并发和并行是同时执行的

线程的生命周期

线程的生命周期
也就是线程从生到死的过程中,经历的各种状态及状态转换。
理解线程这些状态有利于提升并发编程的理解能力。
Java线程的状态
Java总共定义了6种状态
6种状态都定义在Thread类的内部枚举类中。

public class Thread{...public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;}...
}
线程状态说明
NEW(新建)线程刚被创建,但是并未启动。
Runnable(可运行)线程已经调用了start(),等待CPU调度
Blocked(锁阻塞)线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;。
Waiting(无限等待)一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting(计时等待)同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

image-20230810165200048

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

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

相关文章

【Flink】

事件驱动型应用 核心目标&#xff1a;数据流上的有状态计算 Apache Flink是一个框架和分布式处理引擎&#xff0c;用于对无界或有界数据流进行有状态计算。 运行逻辑 状态 把流处理需要的额外数据保存成一个“状态”,然后针对这条数据进行处理,并且更新状态。这就是所谓的“…

c# 中的类

反射 Activator.CreateInstance class Program {static void Main(string[] args){//反射Type t typeof(Student);object o Activator.CreateInstance(t, 1, "FJ");Student stu o as Student;Console.WriteLine(stu.Name);//动态编程dynamic stu2 Activator.Cre…

在 Windows 终端运行已有的 Python 程序

在同一个路径下&#xff0c;输入全名&#xff0c;如图&#xff1a;

【Spring Cloud】Ribbon 实现负载均衡的原理,策略以及饥饿加载

文章目录 前言一、什么是 Ribbon二、Ribbon 实现负载均衡的原理2.1 负载均衡的流程2.2 Ribbon 实现负载均衡的源码剖析 三、Ribbon 负载均衡策略3.1 负载均衡策略3.2 演示 Ribbon 负载均衡策略的更改 四、Ribbon 的饥饿加载4.1查看 Ribbon 的懒加载4.2 Ribbon 的饥饿加载模式 前…

开放式耳机怎么选择、300之内最好的耳机推荐

开放式耳机凭借不入耳、不伤耳、安全更舒适的佩戴体验&#xff0c;得到了越来越多音乐爱好者和专业人士的青睐。开放式耳机不需要插入耳道&#xff0c;在佩戴时可以更加自然和轻松&#xff0c;减少了长时间佩戴引起的不适感&#xff0c;而且不会完全隔绝外界声音&#xff0c;用…

【VIM】VIM配合使用的工具

6-1 课程总结-vim虐我千百遍&#xff0c;我待 vim 如初恋_哔哩哔哩_bilibili

Koa处理请求数据

在开发中&#xff0c;后端接收到请求参数后&#xff0c;需要解析参数。请求分为很多种类型&#xff0c;比如常见的get和post。 请求参数 Koa本身可以解析get请求参数&#xff0c;不能解析post请求参数。例如&#xff1a; router.get(/api/get/userInfo, async (context) >…

多目标平衡优化器黏菌算法(MOEOSMA)求解CEC2020多模式多目标优化

多目标平衡优化器黏菌算法&#xff08;MOEOSMA&#xff09;比现有的多目标黏菌算法具有更好的优化性能。在MOEOSMA中&#xff0c;动态系数用于调整勘探和开采趋势。采用精英存档机制来促进算法的收敛性。使用拥挤距离法来保持Pareto前沿的分布。采用平衡池策略模拟黏菌的协同觅…

手机号码格式校验:@PhoneQuery(作为查询参数)(自定义参数校验注解)

目标 自定义一个用于校验&#xff08;作为查询参数的&#xff09;手机号码格式的注解PhoneQuery&#xff0c;能够和现有的 Validation 兼容&#xff0c;使用方式和其他校验注解保持一致。 校验逻辑 可以为 null 或 空字符串&#xff1b;不能包含空格&#xff1b;必须为数字序…

编程前置:怎么知道一句话的重点?

怎么知道一句话的重点&#xff1f; <small> 之所以要这个问题&#xff0c;是因为① 对标题进行分词 ② 找到标题中的重点词 ③ 然后找到主题中唯一的词语 ④ 然后对这个词语进行绘图说和看&#x1f440;来看&#x1f440;去&#xff0c;也就是文字成图的步骤啦&#xff…

【Spring Cloud】基于 Feign 实现远程调用,深入探索 Feign 的自定义配置、性能优化以及最佳实践方案

前言 在微服务架构中&#xff0c;服务之间的通信是至关重要的&#xff0c;而远程调用则成为实现这种通信的一种常见方式。在 Java 中&#xff0c;使用 RestTemplate 是一种传统的远程调用方式&#xff0c;但它存在一些问题&#xff0c;如代码可读性差、编程体验不一致以及参数…

如何实现电脑语音输入功能?

现在的手机都具备语音输入功能&#xff0c;并且识别率非常高&#xff0c;语音输入是目前最快速的文字输入方式&#xff0c;但是电脑上却无语音输入的功能&#xff0c;那么如何实现在电脑端也可进行语音输入的梦想呢&#xff1f;现在介绍一款小工具“书剑电脑语音输入法”&#…

Codeforces Round 901 (Div. 1) B. Jellyfish and Math(思维题/bfs)

题目 t(t<1e5)组样例&#xff0c;每次给出a,b,c,d,m(0<a,b,c,d,m<2的30次方) 初始时&#xff0c;(x,y)(a,b)&#xff0c;每次操作&#xff0c;你可以执行以下四种操作之一 ①xx&y&#xff0c;&为与 ②xx|y&#xff0c;|为或 ③yx^y&#xff0c;^为异或 …

背包问题

目录 开端 01背包问题 AcWing 01背包问题 Luogu P2925干草出售 Luogu P1048采药 完全背包问题 AcWing 完全背包问题 Luogu P1853投资的最大效益 多重背包问题 AcWing 多重背包问题 I AcWing 多重背包问题 II Luogu P1776宝物筛选 混合背包问题 AcWing 混合背包问题…

QCefView 简介

什么是QCefView QCefView 是为 Qt 开发的一个封装集成了CEF(Chromium Embedded Framework)库的Wdiget UI组件。使用QCefView可以充分发挥CEF丰富强大的Web能力&#xff0c;快速开发混合架构的应用程序。它不需要开发者理解CEF的细节&#xff0c;能够在Qt中更容易的使用CEF&…

IDEA的使用

文章目录 1.IDEA配置1.1 idea界面说明1.2 git1.3 JDK1.4 maven1.5 Tomcat1.6 idea设置编码格式1.7 vscodenodejs1.8 windows下安装redis 2. IDEA问题2.1 setAttribute方法爆红2.2 idea cannot download sources解决办法2.3 springboot项目跑起来不停run 3. vscode3.1 vscode显示…

c++三大概念要分清--重载,隐藏(重定义),覆盖(重写)

目 录 一、重载 **&#xff08;1&#xff09;概念&#xff1a;**在同一个作用域内&#xff1b;函数名相同&#xff0c;参数列表不同&#xff08;参数个数不同&#xff0c;或者参数类型不同&#xff0c;或者参数个数和参数类型都不同&#xff09;&#xff0c;返回值类型可相同也…

uniapp项目实践总结(二十五)苹果 ios 平台 APP 打包教程

导语:当你的应用程序开发完成后,在上架 ios 应用商店之前,需要进行打包操作,下面就简单介绍一下打包方法。 目录 准备工作注册账号生成证书打包配置准备工作 在打包之前,请保证你的 uniapp 应用程序编译到 ios 模拟器或者是真机调试基座环境下是可以正常运行的,苹果打包…

input输入表头保存excel文件

input输入表头 input输入表头 &#xff08;input内除了/&#xff0c;空格 回车 标点符号等 全部作为单元格分隔符&#xff09;保存/storage/emulated/0/代码文件/ 没有就创建文件名命名方法&#xff1a;编号. 库 时间戳嗨&#xff01;听说你有个需求&#xff0c;想根据用户输入…

CSS基础语法第二天

目录 一、复合选择器 1.1 后代选择器 1.2 子代选择器 1.3 并集选择器 1.4 交集选择器 1.4.1超链接伪类 二、CSS特性 2.1 继承性 2.2 层叠性 2.3 优先级 基础选择器 复合选择器-叠加 三、Emmet 写法 3.1HTML标签 3.2CSS 四、背景属性 4.1 背景图 4.2 平铺方式 …