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.xdnf.cn/news/147267.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【Excel】快速提取某个符号前面的数据内容

【问题描述】 在使用excel整理数据过程中&#xff0c;经常与需要调整数据后&#xff0c;进行使用。 例如凭证导出后&#xff0c;科目列是包含科目编码和科目名称的。 但由于要将数据复制到其他的导入模板上使用&#xff0c;对应的模板只需要科目编码&#xff0c;不需要科目名称…

基于Java的校园失物招领平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

愚蠢的代码?某程序员强行编写了一段开发阶段无法调试的代码,大佬们快来救救他!

文章目录 一、开场白1. 程序员打赌的故事2. 目标&#xff1a; 尽量在不修改代码的情况下将springmvc框架以独立jar方式运行 二、出师不利方案一、Spring Web工程转Spring Boot方案二、引入内置服务器jetty其余备用方案 三、柳暗花明遇见jetty-runner测试验证准备工作:部署工作 …

Echarts 教程一

Echarts 教程一 可视化大屏幕适配方案可视化大屏幕布局方案Echart 图表通用配置部分解决方案1. titile2. tooltip3. xAxis / yAxis 常用配置4. legend5. grid6. series7.color Echarts API 使用全局echarts对象echarts实例对象 可视化大屏幕适配方案 rem flexible.js 关于flex…

基于 QT 实现一个 Ikun 专属桌面宠物

Step0、实现思路 想到的思路有两种&#xff1a; 1、使用 QT 的状态机模式&#xff0c;参考官网文档&#xff0c;这个模式的解耦最佳 2、使用原生 Wigets&#xff0c;将窗口设置为透明无框&#xff0c;循环播放桌面宠物的状态 本文采用第二种思路&#xff0c;实现一个极简版…

修炼k8s+flink+hdfs+dlink(一:安装flink)

一&#xff1a;standalone的ha环境部署。 创建目录&#xff0c;上传安装包。 mkdir /opt/app/flink 上传安装包到本目录。 tar -zxvf flink-1.13.6-bin-scala_2.12.tgz配置参数。 在flink-conf.yaml中添加zookeeper配置 jobmanager.rpc.address: node01 high-availability: …

Django基础入门操作 (Django-01)

一 背景介绍 Django是一个开源的 Web应用框架&#xff0c;由Python写成。采用了MTV的框架模式&#xff0c;它最初是被用来做CMS&#xff08;内容管理系统&#xff09;软件。 官方中文文档&#xff1a;Django 文档 | Django 文档 | Django 应用&#xff1a;做内容管理系统(新…

selenium下载安装 -- 使用谷歌驱动碰到的问题

安装教程参考: http://c.biancheng.net/python_spider/selenium.html 1. 谷歌浏览器和谷歌驱动版本要对应(但是最新版本谷歌对应的驱动是没有的,因此要下载谷歌历史其他版本): 谷歌浏览器历史版本下载: https://www.chromedownloads.net/chrome64win/谷歌浏览器驱动下载: http:…

spark SQL 任务参数调优1

1.背景 要了解spark参数调优&#xff0c;首先需要清楚一部分背景资料Spark SQL的执行原理&#xff0c;方便理解各种参数对任务的具体影响。 一条SQL语句生成执行引擎可识别的程序&#xff0c;解析&#xff08;Parser&#xff09;、优化&#xff08;Optimizer&#xff09;、执行…

gwas数据根据eaf Z 和N 求beta和se

https://www.nature.com/articles/s41590-023-01588-w#Sec10

C++基础_Day02

文章目录 四、流程控制语句4.1 选择结构4.1.1 if语句 4.1.2 三目运算符4.1.3 switch语句注意事项 4.1.4 if和switch的区别【CHAT】4.2 循环结构4.2.1 while循环语句4.2.2 do...while循环语句 4.2.3 for循环语句九九乘法表 4.3 跳转语句4.3.1 break语句4.3.2 continue语句4.3.3 …

Nat. Commun. | 大规模高分辨单光子成像

本文由论文作者团队(课题组)投稿 单光子雪崩二极管(Single Photon Avalanche Diode,简称SPAD)阵列因其极佳的单光子灵敏度而受到广泛关注,已广泛应用于量子通信与计算、荧光寿命成像、时间飞行成像等各个领域。与同样具有较高灵敏度的EMCCD和sCMOS相比,SPAD阵列能够在极…

macOS使用官方安装包安装python

新手程序员可能想知道如何在 Mac 上正确安装 Python&#xff0c;这里介绍在 macOS 上安装 Python 的方法。 操作步骤 1.从 Python 官方网站 (python.org) 下载最新的 Python 版本. 单击 macOS 链接并选择最新的 Python 版本。 2.下载完成后&#xff0c;双击包开始安装Python…

【iptables 实战】05 iptables设置网络转发实验

一、网络架构 实验效果&#xff0c;通过机器B的转发功能&#xff0c;将机器A的报文转发到机器C 本实验准备三台机器分别配置如下网络 机器A ip:192.168.56.104 机器C ip:10.1.0.10 机器B 两张网卡&#xff0c;分别的ip是192.168.56.106和10.1.0.11 如图所示 如下图所示 二、…

vertx的学习总结2

一、什么是verticle verticle是vertx的基本单元&#xff0c;其作用就是封装用于处理事件的技术功能单元 &#xff08;如果不能理解&#xff0c;到后面的实战就可以理解了&#xff09; 二、写一个verticle 1. 引入依赖&#xff08;这里用的是gradle&#xff0c;不会吧&#…

ChatGPT多模态升级,支持图片和语音,体验如何?

一、前言 9 月 25 日&#xff0c;ChatGPT 多模态增加了新的语音功能和图像功能。这些功能提供了一种新的、更直观的界面&#xff0c;允许我们与 ChatGPT 进行语音对话或展示我们正在谈论的内容。 ChatGPT 现在可以看、听、和说话了&#xff0c;而不单单是一个文本驱动的工具了。…

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

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

Vue中前端导出word文件

很多时候在工作中会碰到完全由前端导出word文件的需求&#xff0c;因此特地记录一下比较常用的几种方式。 一、提供一个word模板 该方法提供一个word模板文件&#xff0c;数据通过参数替换的方式传入word文件中&#xff0c;灵活性较差&#xff0c;适用于简单的文件导出。需要…

10.1 今日任务:select实现服务器并发

#include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\ }while(0)#define PORT 8888 //端口号&#xff0c;范围1024~49151 #define IP "192.168.112.115" //本机IP&#xff0c;ifco…

【LeetCode热题100】--148.排序链表

148.排序链表 对链表进行排序最适合的算法就是归并排序&#xff1a; 对链表自顶向下归并排序的过程&#xff1a; 找到链表的中点&#xff0c;以中点为分界&#xff0c;将链表拆分成两个子链表&#xff0c;寻找链表的中点可以使用快慢指针的做法&#xff0c;快指针每次移动 2步…