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

多线程JUC

一.什么是多线程,并发和并行

线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程当中,是进程中的实际运作单位

简单理解:应用软件中相互独立,可以同时运行的功能

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

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

二.多线程的实现方式

①继承Thread类的方式进行实现

②实现Runnable接口的方式进行实现

③利用Callable接口和Future接口方式实现

多线程的第一种启动方式:

  1.自己定义一个类继承Thread

  2.重写run方法

  3.创建子类的对象,并启动线程

public class MyThread extends Thread{@Overridepublic void run(){//书写线程要执行的代码for(int i=0;i<10;i++){System.out.println("子线程:"+getName());}}
}
    public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("t1");t2.setName("t2");t1.start();t2.start();}
}

多线程的第二种启动方式:
1. 自己定义一个类实现Runnable接口
2. 重写里面的run方法
3. 创建自己类的对象
4. 创建一个Thread类的对象,并开启线程

public class MyRun implements Runnable{@Overridepublic void run() {//书写线程要执行的代码for(int i=0;i<10;i++){//获取当前线程的对象Thread.currentThread()System.out.println("子线程:"+Thread.currentThread().getName());}}
}
public class ThreadDemo {public static void main(String[] args) {//创建MyRun对象//表示多线程要执行的任务MyRun myRun = new MyRun();//创建Thread对象Thread t1 = new Thread(myRun);Thread t2 = new Thread(myRun);//给线程设置名字t1.setName("线程1");t2.setName("线程2");//启动线程t1.start();t2.start();}
}

前面两种run方法没有返回值,不能获取到多线程运行的结果,所以为了获取到运行结果,第三种方法诞生了

多线程的第三种实现方式:

  特点: 可以获取到多线程运行的结果

  1.创建一个类MyCallable实现Callable接口

  2.重写call(是有返回值的,表示多线程运行的结果)

  3.创建MyCallable的对象(表示多线程要执行的任务)

  4.创建FutureTask对象(作用管理多线程运行的结果)

  5.创建Thread类的对象,并启动(表示线程)

public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {//求1-100的和int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;}return sum;}
}
public class TreadDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建MyCallable的对象(表示多线程要执行的任务)MyCallable mc = new MyCallable();//创建FutureTask对象(作用管理多线程运行的结果)FutureTask<Integer> ft = new FutureTask<>(mc);//创建Thread类的对象,并启动(表示线程)Thread t1 = new Thread(ft);//启动线程t1.start();//获取线程运行结果Integer ft1 = ft.get();System.out.println(ft1);}
}

多线程三种实现方式对比

三.多线程中的常见成员方法

setName细节:

  1. 如果我们没有给线程设置名字,线程也是有默认名字的(格式:Thread-X(X序号,从0开始)
  2. 如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置 

sleep细节:

  1. 哪条线程执行到这个方法,那么哪条线程就会在这停留对应的时间
  2. 方法的参数:就是表示睡眠的时间,单位毫秒(1s=1000ms)
  3. 当时间到了之后,线程会自动的醒来,继续执行下面的其它代码

setDaemon细节:

  1. 当其他的非守护线程执行完毕之后,守护线程会陆续结束
  2. 通俗理解:当女神线程结束了,那么备胎线程也没有存在的必要了

细节:

  • 当JVM虚拟机启动之后,会自动的启动多条线程
  • 其中有一条线程就叫做main线程
  • 它的作用就是去调用main方法,并执行里面的代码
  • 在以前,我们写的所有的代码,其实都是运行在main线程当中

四.线程的生命周期和安全问题

线程的生命周期

问:sleep方法会让线程睡眠,睡眠时间到了之后,立马就会执行下面的代码吗?

答:不会,会先抢夺CPU

线程的安全问题:

public class MyThread extends Thread{//static表示这个类所有的对象,票数是共享的static int ticket=0;//0~99@Overridepublic void run(){while (true){if(ticket<100){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(getName()+"在卖第"+ticket+"张票");}else{break;}}}
}
public class ThreadDemo {public static void main(String[] args) {/*需求:某电影院目前有100张票,3个窗口同时开卖*///创建三个线程MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();//设置线程名称t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//启动线程t1.start();t2.start();t3.start();}
}

五.同步代码块synchonized

  把操作共享数据的代码锁起来

上述问题的解决方案

public class MyThread extends Thread{//static表示这个类所有的对象,票数是共享的static int ticket=0;//0~99//锁对象,一定要是唯一的(static)static Object obj=new Object();@Overridepublic void run(){while (true){//同步代码块synchronized (obj){if(ticket<100){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(getName()+"在卖第"+ticket+"张票");}else{break;}}}}
}

六.同步方法

    就是把synchonized关键字加到方法上

    记住快捷键Ctrl+Alt+M 方法生成

import javax.xml.namespace.QName;public class MyRun implements Runnable{int ticket = 0;//static 没必要加了就 因为我只会建立一个Runable类@Overridepublic void run() {while(true){if (method()) break;}}private  synchronized boolean method() {//thisif(ticket<100){try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");}else return true;return false;}
}

七.lock锁

有没有可能手动释放锁和加锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class MyThread extends Thread{//这个类共享tictekstatic int ticket = 0;static  Lock lock = new ReentrantLock();@Overridepublic void run() {while(true){lock.lock();try {if(ticket<100){Thread.sleep(500);ticket++;System.out.println(getName()+"正在卖第"+ticket+"张票");}else break;} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}

八.死锁

  • 也就是锁出现了锁的嵌套
  • 死锁是什么?是程序的错误,千万不要在程序中涉及死锁

九.生产者和消费者(等待唤醒机制)

    生产者消费者模式是一个十分经典的多线程协作的模式

生产者和消费者常用方法

public class Desk {/*作用:控制生产者和消费者的执行*///是否有面条 0:没有面条 1:有面条public  static int foodFlag=0;//总个数public static int count=10;//锁public static Object lock=new Object();}
public class Foodie extends  Thread{@Overridepublic void run() {/*口诀:1.循环2.同步代码块3.判断共享数据是否到了末尾(到了末尾)4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)*/while(true){synchronized (Desk.lock){if(Desk.count==0){break;}else{//先判断桌子上是否有面条if(Desk.foodFlag==0){//如果没有,则等待try {Desk.lock.wait();//让当前线程和锁进行绑定} catch (InterruptedException e) {throw new RuntimeException(e);}}else{//把吃的总数减一Desk.count--;//如果桌子上有面条,则吃面条System.out.println("吃面条"+"还能再吃"+Desk.count+"碗");//吃完之后,唤醒厨师继续做Desk.lock.notifyAll();//修改桌子状态Desk.foodFlag=0;}}}}}
}
public class Cook extends  Thread{@Overridepublic void run() {/*口诀:1.循环2.同步代码块3.判断共享数据是否到了末尾(到了末尾)4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)*/while (true){synchronized (Desk.lock){if(Desk.count==0){break;}else{//判断桌子上是否有食物if(Desk.foodFlag==1){//如果有,则等待try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else{//如果没有,则制作事物System.out.println("开始制作食物");//修改桌子上的食物状态Desk.foodFlag=1;//叫醒等待的消费者开吃Desk.lock.notifyAll();}}}}}
}
public class ThreadDemo {public static void main(String[] args) {/*** 完成生产者消费者(等待唤醒机制的代码)* 实现线程轮流交替执行的效果*///创建线程的对象Cook cook = new Cook();Foodie foodie = new Foodie();//给线程起个名字cook.setName("厨师");foodie.setName("吃货");//启动线程cook.start();foodie.start();}
}

十.阻塞队列方式实现等待唤醒机制

阻塞队列的继承结构

public class Cook extends  Thread{ArrayBlockingQueue<String> queue;// 构造器public Cook( ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while (true){//不断的把面条放到阻塞队列中try {queue.put("面条");} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("厨师正在做面条");}}
}
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) {throw new RuntimeException(e);}}}
}
public class ThreadDemo {/*** 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码* 细节:生产者和消费者必须使用同一个阻塞队列*/public static void main(String[] args) {//1.创建阻塞队列ArrayBlockingQueue queue = new ArrayBlockingQueue<>(10);//2.创建线程的对象,并把阻塞队列传递过去Cook cook = new Cook(queue);Foodie foodie = new Foodie(queue);//3.启动线程cook.start();foodie.start();}
}

十一.线程池

以前写多线程的弊端:

弊端1:用到线程的时候就创建

弊端2:用完之后线程消失

线程池主要核心原理

  1. 创建一个池子,池子中是空的
  2. 提交任务时,池子就会创建新的线程对象,任务执行完毕,线程归还给池子,下次再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是提交任务时,池子中没有空闲线程。也没法创建新的线程,任务就会排队等待

线程池代码实现:

  1. 创建线程池
  2. 提交任务
  3. 所有的任务全部执行完毕,关闭线程池

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象

public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("子线程i:"+Thread.currentThread().getName());}}
}
public class MyThreadPoolDemo {public static void main(String[] args) {//1.获取线程池对象ExecutorService pool= Executors.newCachedThreadPool();//2.提交任务pool.submit(new MyRunnable());//3.关闭线程池pool.shutdown();}
}

十二.自定义线程池超详细了解

超过最大线程的任务,才会创造临时线程

eg:3个核心线程,3个临时线程,有5个任务,只会创造3个线程,剩下两个排队

eg:3个核心线程,3个临时线程,有8个任务,创造三个核心线程,剩下的排队,创建三个临时线程

任务拒绝策略

import java.lang.String;
import java.util.concurrent.*;public class main {public static void main(String[] args) throws InterruptedException {/*ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂。任务的拒绝策略);参数一:核心线程数量          不能小于0参数二:最大线程数            不能小于0,最大数量>=核心线程数量参数三:空闲线程最大存活时间    不能小于0参数四:时间单位              用TimeUnit指定参数五:任务队列              不能为null参数六:创建线程工厂           不能为null参数七:任务的拒绝策略          不能为null
*/ThreadPoolExecutor pool = new ThreadPoolExecutor(3,  //核心线程数量, 能小于06,       //最大线程数,不能小于0,最大数量>=核心线程数量60,      //空闲线程最大存活时间TimeUnit.SECONDS, //时间单位new ArrayBlockingQueue<>(3),//任务队列Executors.defaultThreadFactory(),//创建线程工厂new ThreadPoolExecutor.AbortPolicy() //任务的拒绝策略);}}

十三.线程池多大合适

什么是最大并行数?

这个与CPU有关,比如4核8线程

  • 4核:CPU有4个大脑——可以做四件事情
  • 因特尔发明了——超线程技术(分身),把4个大脑虚拟成8个

http://www.dtcms.com/a/393091.html

相关文章:

  • Qwen3技术之模型后训练
  • 服务端实现
  • 深入AQS源码:解密Condition的await与signal
  • ceph存储配置大全
  • 数据库造神计划第十六天---索引(1)
  • 【软件推荐】免费图片视频管理工具,让灵感库告别混乱
  • C语言入门教程 | 阶段二:循环语句详解(while、do...while、for)
  • GEO(Generative Engine Optimization)完全指南:从原理到实践
  • Msyql日期时间总结
  • IP地址入门基础
  • 【ROS2】Beginner: CLI tools
  • LeetCode刷题记录----279.完全平方数(Medium)
  • H7-TOOL的250M示波器模组采集CANFD标准波形效果,开口逻辑0,闭口逻辑1
  • 打工人日报#20250920
  • 详解C/C++内存管理
  • SSM(springboot部分)
  • C++ std:string和Qt的QString有哪些差异?
  • FunASR开源项目实战:解锁语音识别新姿势
  • (华为杯)数学建模比赛编程助手
  • 通义千问对postgresql wire协议的连接和执行SQL过程的解释
  • 钣金折弯机被远程锁机了怎么办
  • 基于陌讯AIGC检测算法的高性能部署实践:FastAPI与多进程并发设计详解
  • 群晖 NAS 远程访问痛点解决:神卓 N600 公网 IP 盒实战体验
  • JavaWeb之HttpServletRequest与HttpServletResponse详解及快递管理系统实践
  • Git详细介绍
  • 大话计算机网络(上)
  • JVM方法调用机制深度解析:从aload_1到invokevirtual的完整旅程
  • STM32CubeIDE学习——安装
  • 追觅宣布进军手机市场,已经白热化的手机赛道追觅优势何在?
  • AI智能体开发工作流的成功案例分享及思路