当前位置: 首页 > news >正文

【Java从入门到大牛】多线程

🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:Java从入门到大牛
🌠 首发时间:2023年11月18日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾

目录

  • 多线程的创建
    • 方式一:继承Thread类
    • 方式二:实现Runnable接口
    • 方式三:实现Callable接口
  • Thread的常见方法
  • 线程安全
    • 什么是线程安全问题
    • 用程序模拟线程安全问题
  • 线程同步
    • 认识线程同步
    • 方式一:同步代码块
    • 方式二:同步方法
    • 方式三:Lock锁
  • 线程通信【了解】
  • 线程池
    • 认识线程池
    • 创建线程池
    • 线程池处理Runnable任务
    • 线程池处理Callable任务
    • Executors工具类实现线程池
  • 其它细节知识:并发、并行
  • 其它细节知识:线程的生命周期
  • 乐观锁
  • 多线程练习题

多线程的创建

方式一:继承Thread类

什么是线程?

线程(Thread)是一个程序内的一条执行流程,比如常见的 main 方法就是一个线程

public static void main(String[] args) {// 代码...for(int i = 0; i <10; i++) {System.out.println(i);}//...
}

程序中如果只有一条执行流程,那么这个程序就是单线程的程序

什么是多线程?

多线程是指从软硬件上实现的多条执行流程的技术(多条线程由 CPU 负责调度执行)

多线程的用处

在生活中,有很多多线程应用的例子,比如,12306网站同时有多位用户在抢票,那么网站就需要提供多条线程来为多位用户服务;在使用百度网盘时,用户可能同时需要上传和下载资源,这也需要用到多线程技术;再比如消息通信、淘宝、京东系统都离不开多线程技术

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

Java 是通过 java.lang.Thread 类的对象来代表线程的

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

  1. 定义一个子类 MyThread 继承线程类 java.lang.Thread,并重写 run() 方法
  2. 创建 MyThread 类的对象
  3. 调用线程对象的 start() 方法启动线程,这个方法会自动执行 run() 方法

代码实现

MyThread类

/*** 1. 让子类继承Thread线程类*/
public class MyThread extends Thread {@Override   // 2. 必须重写Thread类的run方法public void run() {// 这里用来描述线程要执行的任务for (int i = 1; i <= 5; i++) {System.out.println("子线程MyThread输出:" + i);}}
}

测试类

/*** 目标:掌握线程的创建方式一:继承Thread类*/
public class ThreadTest1 {// main方法是由一条默认的主线程负责执行的public static void main(String[] args) {// 3. 创建MyThread线程类的对象代表一个线程Thread t = new MyThread();  // 多态写法// 4. 启动线程,必须是调用start方法t.start();  // 启动后,现在就存在两个线程:main线程和t线程for (int i = 1; i <= 5; i++) {System.out.println("主线程main输出:" + i);}}
}

因为有两个线程,线程执行的顺序也不确定,所有每次执行结果都可能不同

在这里插入图片描述

方法一优缺点:

  • 优点:编码简单
  • 缺点:线程类已经继承 Thread 类,所以无法再继承其他类,不利于功能的扩展

多线程的注意事项

1、启动线程必须是调用 start 方法,不能调用 run 方法

  • 直接调用 run 方法会当成普通方法执行,此时相当于还是单线程执行
  • 只有调用 start 方法才是启动一个新的线程执行

2、不要把主线程任务放在启动子线程之前

  • 这样主线程一直是先跑完的,相当于是一个单线程的效果了

方式二:实现Runnable接口

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

  1. 定义一个线程任务类 MyRunnable 实现 Runnable 接口,并重写 run() 方法

  2. 创建 MyRunnable 任务对象

  3. 把 MyRunnable 任务对象交给 Thread 处理
    在这里插入图片描述

  4. 调用线程对象的 start() 方法启动线程

代码实现

MyRunnable类

/*** 1.定义一个任务类,实现Runnable接口*/
public class MyRunnable implements Runnable {// 2.重写Runnable的run方法@Overridepublic void run() {// 描述线程要执行的任务for (int i = 1; i <= 5; i++) {System.out.println("子线程输出:" + i);}}
}

测试类

/*** 目标:掌握多线程的创建方式二:实现Runnable接口*/
public class ThreadTest2 {public static void main(String[] args) {// 3.创建任务对象Runnable target = new MyRunnable();// 4.把任务对象交给一个线程对象处理new Thread(target).start();for (int i = 1; i <= 5; i++) {System.out.println("主线程main输出:" + i);}}
}

方式二的优缺点

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

线程创建方式二的匿名内部类写法

  1. 可以创建 Runnable 的匿名内部类对象
  2. 再交给 Thread 线程对象
  3. 再调用线程对象的 start() 启动线程

匿名内部类写法实现

/*** 目标:掌握多线程创建方式二的匿名内部类写法*/
public class ThreadTest2_2 {public static void main(String[] args) {// 1、直接创建Runnable接口的匿名内部类形式(任务对象)Runnable target = new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 5; i++) {System.out.println("子线程1输出:" + i);}}};new Thread(target).start();// 简化形式1:new Thread(new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 5; i++) {System.out.println("子线程2输出:" + i);}}}).start();// 简化形式2:new Thread(() -> {for (int i = 1; i <= 5; i++) {System.out.println("子线程3输出:" + i);}}).start();for (int i = 1; i <= 5; i++) {System.out.println("主线程main输出:" + i);}}
}

方式三:实现Callable接口

前两种线程创建方式都存在一个问题:假如线程执行完毕后有一些数据需要返回,他们重写的 run 方法均不能返回结果

怎么解决这个问题?

  • JDK5.0 提供了 Callable 接口和 FutureTask 来实现
  • 这种方式最大的优点就是可以返回线程执行完毕后的结果

多线程的第三种创建方式:利用Callable接口和FutureTask类来实现

1、创建任务对象

  • 定义一个类实现 Callable 接口,并重写 call 方法,里面封装要做的事情和要返回的数据
  • 把 Callable 类型的对象封装成 FutureTask(线程任务对象)

2、把线程任务对象交给 Thread 对象

3、调用 Thread 对象的 start 方法启动线程

4、线程执行完毕后,通过 FutureTask 对象的 get 方法去获取线程任务执行的结果

FutureTask的API

在这里插入图片描述
代码实现

MyCallable类

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方法@Overridepublic String call() throws Exception {// 描述线程的任务,返回线程执行返回后的结果// 需求:求1-n的和并返回int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return "线程求出了1-" + n + "的和是:" + sum;}
}

测试类

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;/*** 目标:掌握线程的创建方式三:实现Callable接口*/
public class ThreadTest3 {public static void main(String[] args) throws Exception {// 3、创建一个Callable的对象Callable<String> call = new MyCallable(100);// 4、把Callable的对象封装成一个FutureTask对象(任务对象)// 未来任务对象的作用?// 1、是一个任务对象,实现了Runnable对象// 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果FutureTask<String> f1  = new FutureTask<>(call);// 5、把任务对象交给一个Thread对象new Thread(f1).start();Callable<String> call2 = new MyCallable(200);FutureTask<String> f2  = new FutureTask<>(call2);new Thread(f2).start();// 6、获取线程执行完毕后返回的结果// 注意:如果执行到这儿,假如上面的线程还没有执行完毕// 这里的代码会暂停,等待上面线程执行完毕后才会获取结果String rs = f1.get();System.out.println(rs);String rs2 = f2.get();System.out.println(rs2);}
}

在这里插入图片描述

线程创建方式三的优缺点

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

Thread的常见方法

Thread 提供了很多与线程操作相关的方法
在这里插入图片描述

应用演示

MyThread类

public class MyThread extends Thread {public MyThread(String name) {super(name);    // 为当前线程设置名字}@Overridepublic void run() {// 获取当前线程对象Thread t = Thread.currentThread();for (int i = 1; i <= 3; i++) {System.out.println(t.getName() + "输出:" + i);}}
}

测试类1

/*** 目标:掌握Thread的常用方法*/
public class ThreadTest1 {public static void main(String[] args) {Thread t1 = new MyThread("1号线程");t1.start();System.out.println(t1.getName());   // Thread-0Thread t2 = new MyThread("2号线程");t2.start();System.out.println(t2.getName());   // Thread-1//获取主线程对象的名字Thread m = Thread.currentThread();m.setName("main主线程");System.out.println(m.getName());for (int i = 1; i <= 5; i++) {System.out.println(m.getName() + "输出:" + i);}}
}

在这里插入图片描述

测试类2

/*** 目标:掌握sleep方法,join方法的作用*/
public class ThreadTest2 {public static void main(String[] args) throws Exception {for (int i = 1; i <= 5; i++) {System.out.println(i);// 休眠5sif(i == 3){// 让当前执行的线程暂停5秒,再继续执行Thread.sleep(5000);}}// join方法作用:让当前调用这个方法的线程先执行完再执行其他线程Thread t1 = new MyThread("1号线程");t1.start();t1.join();Thread t2 = new MyThread("2号线程");t2.start();t2.join();Thread t3 = new MyThread("3号线程");t3.start();t3.join();}
}

在这里插入图片描述

Thread其他方法的说明

Thread 类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法,在开发中很少使用,在这里就不一一列出了

线程安全

什么是线程安全问题

什么是线程安全问题?

多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题

取钱的线程安全问题

场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?

有可能会出现2人都成功取钱10万元的结果

用程序模拟线程安全问题

需求

小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,模拟2人同时去取钱10万元

分析

  1. 需要提供一个账户类,接着创建一个账户对象代表2个人的共享账户
  2. 需要定义一个线程类(用于创建两个线程,分别代表小明和小红)
  3. 创建两个线程,传入同一个账户对象给两个线程处理
  4. 启动两个线程,同时去同一个账户对象中取钱10万元

代码实现

账户类

public class Account {private String cardId; // 卡号private double money; // 余额。public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}// 取钱public void drawMoney(double money) {// 先搞清楚是谁来取钱?String name = Thread.currentThread().getName();// 1、判断余额是否足够if(this.money >= money){System.out.println(name + "来取钱" + money + "成功!");this.money -= money;System.out.println(name + "来取钱后,余额剩余:" + this.money);}else {System.out.println(name + "来取钱:余额不足~");}}// get、set方法public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}
}

线程类

public class DrawThread extends Thread{private Account acc;public DrawThread(Account acc, String name){super(name);this.acc = acc;}@Overridepublic void run() {acc.drawMoney(100000);  // 取钱}
}

测试类

/*** 目标:模拟线程安全问题*/
public class ThreadTest {public static void main(String[] args) {// 1、创建一个账户对象,代表两个人的共享账户Account acc = new Account("ICBC-110", 100000);// 2、创建两个线程,分别代表小明 小红,再去同一个账户对象中取钱10万new DrawThread(acc, "小明").start(); // 小明new DrawThread(acc, "小红").start(); // 小红}
}

在这里插入图片描述

线程同步

认识线程同步

线程同步

解决线程安全问题的方案

线程同步的思想

让多个线程实现先后依次访问共享资源,这样就解决了安全问题

线程同步的常见方案

加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来

方式一:同步代码块

同步代码块

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

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

原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行

同步锁的注意事项

对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug

同步代码块的实现

IDEA里加同步锁的快捷方式:鼠标选中要上锁的核心代码,按住 Ctrl+Alt+T,然后选择第9项 synchronized 即可

依旧是前面那个取钱的例子,我们只需要将账户类稍微修改一下即可,代码如下

public class Account {private String cardId; // 卡号private double money; // 余额// ...// 取钱public void drawMoney(double money) {// 先搞清楚是谁来取钱?String name = Thread.currentThread().getName();// 1、判断余额是否足够// this正好代表共享资源!synchronized (this) {if(this.money >= money){System.out.println(name + "来取钱" + money + "成功!");this.money -= money;System.out.println(name + "来取钱后,余额剩余:" + this.money);}else {System.out.println(name + "来取钱:余额不足~");}}}// 如果是静态方法,建议使用 类名.class 作为锁public static void test(){synchronized (Account.class){}}// ...
}

在这里插入图片描述

问题:锁对象随便选择一个唯一的对象好不好呢?

不好,可能会影响其他无关线程的执行

锁对象的使用规范

  • 建议使用共享资源作为锁对象,对于实例方法建议使用 this 作为锁对象
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象

方式二:同步方法

同步方法

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

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

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

同步方法底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码
  • 如果方法是实例方法:同步方法默认用 this 作为锁对象
  • 如果方法是静态方法:同步方法默认用 类名.class 作为锁对象

同步方法代码实现

账户类修改如下

public class Account {private String cardId; // 卡号private double money; // 余额// ...// 同步方法public synchronized void drawMoney(double money) {// 先搞清楚是谁来取钱?String name = Thread.currentThread().getName();// 1、判断余额是否足够if(this.money >= money){System.out.println(name + "来取钱" + money + "成功!");this.money -= money;System.out.println(name + "来取钱后,余额剩余:" + this.money);}else {System.out.println(name + "来取钱:余额不足~");}}// ...
}

方式三:Lock锁

Lock锁

  • Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大

  • Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象

    在这里插入图片描述

Lock的常用方法
在这里插入图片描述

Lock锁代码实现

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 void drawMoney(double money) {// 先搞清楚是谁来取钱?String name = Thread.currentThread().getName();// 为了防止try里面的代码块出现异常而导致无法解锁,// 我们需要对其进行异常处理,并将解锁操作放到finally里面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(); // 解锁}}// ...
}

线程通信【了解】

什么是线程通信?

当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺

线程通信的常见模型(生产者与消费者模型)

  • 生产者线程负责生产数据
  • 消费者线程负责消费生产者生产的数据
  • 注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完也应该等待自己,再通知生产者生产

Object类的等待和唤醒方法
在这里插入图片描述

注意:上述方法应该使用当前同步锁对象进行调用

为了更好地理解生产者与消费者模型,我们来看一个例子:

  • 3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上
  • 2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃
  • 同一时刻,只能有一个生产者生产包子,或只能有一个消费者吃包子

代码实现

桌子类

import java.util.ArrayList;
import java.util.List;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();}}
}

测试类

/*** 目标:了解一下线程通信*/
public class ThreadTest {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();}
}

在这里插入图片描述

线程池

认识线程池

什么是线程池?

线程池就是一个可以复用线程的技术

不使用线程池的问题

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

线程池的工作原理
在这里插入图片描述

创建线程池

Java中谁代表线程池?

JDK5.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:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)

创建一个线程池

public class ThreadPoolTest1 {public static void main(String[] args) {// 1、通过ThreadPoolExecutor创建一个线程池对象ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());}
}

线程池的注意事项

1、临时线程什么时候创建?

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程

2、什么时候会开始拒绝新任务?

  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务

线程池处理Runnable任务

ExecutorService的常用方法
在这里插入图片描述

新任务拒绝策略

在这里插入图片描述

代码实现

MyRunnable类

public class MyRunnable implements Runnable{@Overridepublic void run() {// 描述任务System.out.println(Thread.currentThread().getName() + " ==> 输出666~~");try {Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();}}
}

测试类

import java.util.concurrent.*;public class ThreadPoolTest1 {public static void main(String[] args) {// 1、通过ThreadPoolExecutor创建一个线程池对象ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());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);// pool.shutdown(); // 等着线程池的任务全部执行完毕后,再关闭线程池// pool.shutdownNow(); // 立即关闭线程池!不管任务是否执行完毕!}
}

在这里插入图片描述

线程池处理Callable任务

ExecutorService的常用方法
在这里插入图片描述

代码实现

MyCallable类

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方法@Overridepublic String call() throws Exception {// 描述线程的任务,返回线程执行返回后的结果// 需求:求1-n的和返回int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return Thread.currentThread().getName() + "求出了1-" + n + "的和是:" + sum;}
}

测试类

import java.util.concurrent.*;public class ThreadPoolTest2 {public static void main(String[] args) throws Exception {// 1、通过ThreadPoolExecutor创建一个线程池对象ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), 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));System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());}
}

在这里插入图片描述

Executors工具类实现线程池

什么是Executors

Executors 是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象

在这里插入图片描述

注意:这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象

Executors使用可能存在的陷阱

大型并发系统环境中,使用Executors如果不注意可能会出现系统风险。

在这里插入图片描述

其它细节知识:并发、并行

进程

  • 正在运行的程序(软件)就是一个独立的进程
  • 线程是属于进程的,一个进程中可以同时运行很多个线程
  • 进程中的多个线程其实是并发和并行执行的

并发的含义

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

并行的理解

在同一个时刻上,同时有多个线程在被CPU调度执行。

其它细节知识:线程的生命周期

什么是线程的生命周期

  • 也就是线程从生到死的过程中,经历的各种状态及状态转换
  • 理解线程这些状态有利于提升并发编程的理解能力

Java线程的状态

  • Java中总共定义了6种状态
  • 6种状态都定义在Thread类的内部枚举类中
    在这里插入图片描述

线程的6种状态互相转换

在这里插入图片描述

线程的6种状态总结

在这里插入图片描述

乐观锁

前面我们学到的,都是悲观锁,就是一上来就加锁,没有安全感,每次只能一个线程进入访问完毕后,再解锁,线程安全,但是性能较差

什么是乐观锁

一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制,线程安全,并且性能较好

例子

有1个变量,100个线程,每个线程都要对其加100次

不加锁

MyRunnable.java

public class MyRunnable implements Runnable{private int cnt;     // 记录浏览人数@Overridepublic void run() {// 加100次for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " cnt --------> " + (++cnt));}}
}

Test1.java

public class Test1 {public static void main(String[] args) {// 需求:1个变量,100个线程,每个线程对其加100次Runnable target = new MyRunnable();for (int i = 0; i < 100; i++) {new Thread(target).start();}}
}

加的顺序不一定是一个一个地加,还有可能出现加不到10000的情况,因为有可能在同一时刻两个线程同时抢到了执行权,对同一个数字进行加1,结果相同,但是少了一次加法

在这里插入图片描述

加悲观锁

MyRunnable.java

public class MyRunnable implements Runnable{private int cnt;     // 记录浏览人数@Overridepublic void run() {// 加100次for (int i = 0; i < 100; i++) {synchronized(this) {System.out.println(Thread.currentThread().getName() + " cnt --------> " + (++cnt));}}}
}

没有问题

在这里插入图片描述

加乐观锁

加乐观锁后,当有两个线程同时抢到执行权时,一个线程在对变量修改完成后,要赋值回去的时候会进行判断,若变量已经被修改了,则这一轮修改作废,重新进行一轮修改

MyRunnable2.java

public class MyRunnable2 implements Runnable{// 整数修改的乐观锁:用原子类实现private AtomicInteger cnt = new AtomicInteger();@Overridepublic void run() {// 加100次for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " cnt --------> " + cnt.incrementAndGet());}}
}

Test2.java

public class Test2 {public static void main(String[] args) {// 需求:1个变量,100个线程,每个线程对其加100次Runnable target = new MyRunnable2();for (int i = 0; i < 100; i++) {new Thread(target).start();}}
}

在这里插入图片描述

多线程练习题

需求描述

有100份礼品,小红、小明两人同时发送,当剩下的礼品数小于10份的时候则不再送出,利用多线程模拟该过程并将线程的名称打印出来,并最后在控制台分别打印小红、小明分别送出了多少份礼物

代码

SendThread.java

import java.util.List;
import java.util.Random;// 发送礼品
public class SendThread extends Thread {private List<String> gift;private int cnt;    // 计数public SendThread(List<String> gift, String name) {super(name);this.gift = gift;}@Overridepublic void run() {// 发礼品// 注意:锁必须唯一Random r = new Random();String name = Thread.currentThread().getName();while (true) {synchronized(gift) {if (gift.size() < 10) {break;}String rs = gift.remove(r.nextInt(gift.size()));System.out.println(name + "发出了:" + rs);cnt++;}}}public int getCnt() {return cnt;}
}

Demo.java

import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class Demo1 {public static void main(String[] args) throws Exception {// 创建100份礼品List<String> gift = new ArrayList<>();String[] names = {"口红", "鲜花", "包包", "剃须刀", "皮带", "手表"};Random r = new Random();    // 随机分配礼品名称for (int i = 0; i < 100; i++) {gift.add(names[r.nextInt(names.length)] + (i+1));}System.out.println(gift);// 定义线程类,创建线程对象,去集合中拿礼品给别人SendThread xh = new SendThread(gift, "小红");xh.start();SendThread xm = new SendThread(gift, "小明");xm.start();xh.join();xm.join();System.out.println("小红送出了" + xh.getCnt() + "份礼物");System.out.println("小明送出了" + xm.getCnt() + "份礼物");}
}

在这里插入图片描述

相关文章:

【Java从入门到大牛】多线程

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Java从入门到大牛 &#x1f320; 首发时间&#xff1a;2023年11月18日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f4…...

UE5 C++报错:is not currently enabled for Live Coding

解决办法&#xff1a; 再次打开项目&#xff0c;以此法打开&#xff1a;...

mysql服务器数据同步

在Linux和Windows之间实现MySQL服务器数据的同步。下面是一些常见的方法和工具&#xff1a; 复制&#xff08;Replication&#xff09;&#xff1a;MySQL复制是一种常见的数据同步技术&#xff0c;可用于将一个MySQL服务器的数据复制到其他服务器。您可以设置主服务器&#xff…...

Docker Golang 开发环境搭建指南

Docker Golang 开发环境搭建指南 概述 在 Golang 开发中&#xff0c;搭建合适的开发环境是非常重要的。然而&#xff0c;由于 Golang 的跨平台特性&#xff0c;不同操作系统之间的配置差异可能会导致环境搭建过程变得复杂。为了简化这个过程并保持开发环境的一致性&#xff0…...

MFC保存窗口客户区为图片

首先的窗口输出一些内容&#xff1b; 菜单单击函数代码&#xff1b; void CgetmypicView::OnTestGetmypic() {// TODO: 在此添加命令处理程序代码HWND hwnd this->GetSafeHwnd();HDC hDC ::GetWindowDC(hwnd);//获取DC RECT rect;::GetClientRect(hwnd, &rect)…...

JAVA安全之Shrio550-721漏洞原理及复现

前言 关于shrio漏洞&#xff0c;网上有很多博文讲解&#xff0c;这些博文对漏洞的解释似乎有一套约定俗成的说辞&#xff0c;让人云里来云里去&#xff0c;都没有对漏洞产生的原因深入地去探究..... 本文从现象到本质&#xff0c;旨在解释清楚Shrio漏洞是怎么回事&#xff01…...

有Mac或无Mac电脑通用的获取安卓公钥的方案

从2023年9月开始&#xff0c;所有上架应用市场的app都需要进行APP备案。 其中后端服务器在阿里云的可以在阿里云备案&#xff0c;后端服务器在腾讯云的可以在腾讯云备案。但无论你是在什么云厂商里做备案&#xff0c;无一例外的是&#xff0c;无论是上架安卓应用还是上架IOS应…...

电池故障估计:Realistic fault detection of li-ion battery via dynamical deep learning

昇科能源、清华大学欧阳明高院士团队等的最新研究成果《动态深度学习实现锂离子电池异常检测》&#xff0c;用已经处理的整车充电段数据&#xff0c;分析车辆当前或近期是否存在故障。 思想步骤&#xff1a; 用正常电池的充电片段数据构造训练集&#xff0c;用如下的方式构造…...

微服务和Spring Cloud Alibaba介绍

1、微服务介绍 1.1 系统架构演变 随着互联网的发展&#xff0c;网站应用的规模也在不断的扩大&#xff0c;进而导致系统架构也在不断的进行变化。从互联网早起到现在&#xff0c;系统架构大体经历了下面几个过程: 单体应用架构 —> 垂直应用架构 —> 分布 式架构—>…...

【js】 lodash命名转换和封装

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ lodash转换函数h3与underscore比较 2️⃣ 实战&#xff1a;对象属性名转换函数封装单元测试 &#x1f6ec; 文章小结&#x1f4d6; 参考资料 &#x1f6eb; 导读 需求 爬虫中经常出现各种类型的命名&#xff0c;往往一个对象…...

RK3568驱动指南|第七篇 设备树-第67章 of操作函数实验:获取属性

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…...

vue3安装vue-router

环境 node 18.14.2 yarn 1.22.19 windows 11 vite快速创建vue项目 参考 安装vue-touter 官网 yarn add vue-router4src下新建router文件夹&#xff0c;该文件夹下新建index.ts // router/index.ts 文件 import { createRouter, createWebHashHistory, RouterOptions, Ro…...

〖大前端 - 基础入门三大核心之JS篇㊱〗- JavaScript 的DOM节点操作

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;不渴望力量的哈士奇(哈哥)&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xf…...

【计算机基础】优雅的PPT就应该这样设计

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…...

Vatee万腾的科技征程:Vatee数字化创新的前沿探讨

在Vatee万腾的科技征程中&#xff0c;我们目睹了一场数字化创新的引领之旅&#xff0c;探讨了Vatee在科技前沿的独到见解。Vatee万腾不仅仅是一家科技公司&#xff0c;更是一支前行不辍的冒险队伍&#xff0c;通过不断突破自我&#xff0c;探索未知领域&#xff0c;引领着数字化…...

【PB续命05】WinHttp.WinHttpRequest的介绍与使用

0 WinHttp.WinHttpRequest简介 winhttp.winhttprequest是Windows操作系统中的一个API函数&#xff0c;用于创建和发送HTTP请求。它可以用于从Web服务器获取数据&#xff0c;或将数据发送到Web服务器。该函数提供了许多选项&#xff0c;例如设置请求头、设置代理服务器、设置超…...

【Linux】进程间是这样通信的--管道篇

TOC 目录 进程间通信的介绍 进程间通信的概念 进程间通信的目的 进程间通信的本质 进程间通信的分类 管道 什么是管道 匿名管道 pipe函数 匿名管道使用步骤 管道读写规则 管道的特点 1、管道内部自带同步与互斥机制 2、管道的生命周期随进程 3、管道提供的是流式…...

Python基础入门例程60-NP60 跳过列表的某个元素(循环语句)

最近的博文: Python基础入门例程59-NP59 提前结束的循环(循环语句)-CSDN博客 Python基础入门例程58-NP58 找到HR(循环语句)-CSDN博客 Python基础入门例程57-NP57 格式化清单(循环语句)-CSDN博客 目录 最近的博文: 描述...

三十二、W5100S/W5500+RP2040树莓派Pico<UPnP示例>

文章目录 1 前言2 简介2 .1 什么是UPnP&#xff1f;2.2 UPnP的优点2.3 UPnP数据交互原理2.4 UPnP应用场景 3 WIZnet以太网芯片4 UPnP示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 随着智能家居、物联网等…...

2023.11.18 Hadoop之 YARN

1.简介 Apache Hadoop YARN &#xff08;Yet Another Resource Negotiator&#xff0c;另一种资源协调者&#xff09;是一种新的 Hadoop 资源管理器&#xff0c;它是一个通用资源管理系统和调度平台&#xff0c;可为上层应用提供统一的资源管理和调度。支持多个数据处理框架&…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)

UniApp 集成腾讯云 IM 富媒体消息全攻略&#xff08;地理位置/文件&#xff09; 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型&#xff0c;核心实现方式&#xff1a; 标准消息类型&#xff1a;直接使用 SDK 内置类型&#xff08;文件、图片等&#xff09;自…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...