java基础概念63-多线程

server/2025/2/4 0:20:08/

一、线程VS进程

1-1、进程

进程是程序的基本执行实体

每一个正在运行的软件都是一个进程。

一个进程可以包含多个线程

1-2、线程

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

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

每个线程都有自己的执行路径和栈空间,它们可以并发执行,共享进程的资源

如:内存、文件句柄等,例如,在一个浏览器进程中,可能有一个线程负责显示页面内容,另一个线程负责下载图片,还有一个线程负责处理用户的输入操作。

二、多线程的概念

        多线程是指在一个程序中同时运行多个线程,这些线程可以并行并发执行。

        并行是指多个线程在多核处理器上同时执行,而并发是指多个线程在单核处理器上通过时间片轮转的方式交替执行。

 

2-1、多线程的应用场景

  • 软件中的耗时操作
  • 拷贝、迁移大文件
  • 加载大量的资源文件
  • 所有的聊天软件
  • 所有的服务器

2-3、小结

 

2-4、并发VS并行

1、并发

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

2、并行

在同一时刻,有多个指令在多个CPU同时执行。

 

 

在计算机中,并发和并行可能同时发生。当2核4线程,处理4个线程的时候,就是并行;但是处理多于4个线程的时候,CPU的运行就是随机并发选取多个线程,并行执行!

三、多线程的实现方式

  • 继承Thread类的方式进行实现
  • 实现Runnable接口的方式进行实现
  • 利用Callable接口和Future接口方式实现

3-1、继承Thread类

  • 继承Thread类;
  • 重写run方法;
  • 使用start方法调用线程。

3-2、实现Runnable接口

  • 实现Runnable接口
  • 实现run方法
  • 创建自定义类的对象
  • 创建Thread类的对象。

3-3、 利用Callable接口和Future接口

前两种方式,重写的run方法没有返回值,不能获取到多线程运行的结果!

 若是想要获取多线程的运行结果:

 

3-4、多线程三种实现方式的对比

四、线程中常见的成员方法

4-1、线程的名字

若是没有给线程设置名字,线程是有默认名字的,格式:Thread-x(x序号,从0开始!) 

设置线程名字:

1、setName方法

2、Thread构造方法

4-2、currentThread()方法:获取当前线程 

【细节】:

        当JVM虚拟机启动之后,会自动的启动多条线程,其中有一条线程就叫做main线程,他的作用就是去调用main方法,并执行里面的代码。

        在以前,我们写的所有的代码,其实都是运行在main线程当中。

4-3、sleep()方法

【细节】:

1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间;

2、方法的参数:就表示睡眠的时间,单位毫秒
1秒= 1000秒
3、当时间到了之后,线程会自动的醒来,继续执行下面的其代码。

 

4-4、线程的优先级

1、线程的调度

线程调度是操作系统或 Java 虚拟机(JVM)为了合理分配 CPU 时间片给各个线程,以实现多线程并发执行而进行的一系列操作。

抢占式调度

CPU在哪个时候执行哪条现成,不确定!执行时间也不确定。都是随机的。

优先级越大,线程抢到CPU的概率越大。

java中线程的优先级1~10,默认是5。

非抢占式调度

所有的线程轮流执行,执行的时间也差不多!

2、设置线程的优先级

【注意】:

优先级越高,只是抢到CPU的概率越大,但是,不是100%。 

4-5、设置守护线程

应用场景:

        Java 的垃圾回收器(GC)就是一个典型的守护线程。它在后台不断运行,负责回收不再使用的内存对象,释放系统资源。当所有的非守护线程(如主线程、业务逻辑线程等)执行完毕后,JVM 会退出,此时垃圾回收线程也会自动停止运行。

 

        在大型应用程序中,需要实时监控日志文件的变化,以便及时发现异常情况。可以创建一个守护线程,定期检查日志文件的大小、内容等信息。当应用程序的所有业务线程结束后,这个日志监控的守护线程会自动终止,不会影响 JVM 的退出。

4-6、出让线程

让出CPU的执行权,出让线程会让结果均匀一点,但不是绝对的! 

4-7、插入线程

join 方法的主要作用是让当前线程等待调用 join 方法的线程执行完毕后再继续执行。简单来说,就是一个线程可以通过调用另一个线程的 join 方法来等待该线程结束。

原本main线程和土豆线程,交替在控制台打印,土豆线程插入后,土豆线程执行完,main线程再执行!

 

五、线程的生命周期

有没有执行权:能不能去抢CPU。

睡眠时间到了之后,线程需要重新去抢CPU,抢到了才能执行下面的代码!

六、线程安全的问题

6-1、多线程的问题

需求:3个窗口一共卖100张电影片

【注意】:

        若是用implements Runnable方式,则ticket变量不用static修饰!因为此方法,只会创建一个自定义对象! 

问题结果: 

 

问题原因:

CPU在执行的时候,随时会被抢走!且线程执行具有随机性

解决方式:将操作共享数据的代码锁起来!

6-2、同步代码块

synchronized 关键字是用于实现线程同步的重要工具,它可以保证在同一时刻只有一个线程能够访问被 synchronized 修饰的代码块或方法,从而避免多线程并发访问共享资源时可能出现的数据不一致问题。

或者是:

java">synchronized (MyThreadMain.class){// 同步代码
}

【注意】:

同步代码块在while里面! 

 

 6-3、同步方法

【技巧】:

先写同步代码块,再将同步代码块中的方法抽取成同步方法中。 

java">@Overridepublic void run() {while (true) {if (method()) break;}}// 锁对象是thisprivate synchronized boolean method() {if (ticket < 100) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正买卖第" + ticket + "张票");} else {return true;}return false;}

 

 6-4、拓展:StringBuilder和StringBuffer

字符串在拼接的时候,除了StringBuilder还有StringBuffer。两者方法一模一样!

StringBuilder的实例用于多个线程是不安全的,如果需要线程的同步,则建议使用StringBuffer。因为StringBuffer中所有的方法都是同步方法。

所以,若是你的代码是单线程的,可以用StringBuilder,但是,若是多线程,建议用StringBuffer。

 

6-4、lock锁 

对于同步代码块、同步方法,锁的开关是自动关闭,自动打开的,自己没办法控制。

想要手动的加锁、释放锁,使用lock方法:

示例:

java">public class MyThreadMain {public static void main(String[] args) {MyThread myThread1 = new MyThread("窗口1");MyThread myThread2 = new MyThread("窗口2");MyThread myThread3 = new MyThread("窗口3");myThread1.start();myThread2.start();myThread3.start();}
}
java">public class MyThread extends Thread {public MyThread() {}public MyThread(String name) {super(name);}// 0~99static int ticket = 0;// 因为MyThread可能创建多个,所以,要保证锁对象唯一!static Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {//synchronized (MyThread.class) {lock.lock();try {if (ticket < 100) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正买卖第" + ticket + "张票");} else {break;}} catch (Exception e) {e.printStackTrace();}finally {// 无论是否break,都要执行!lock.unlock();}//}}}}

 

 七、死锁

锁的嵌套!是一个错误。

八、等待唤醒机制

8-1、生产者和消费者

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

至少有三个角色:

  • 生产者;
  • 消费者;
  • 控制生产者和消费者的执行。

1、生产者等待模式

2、消费者等待模式 

3、常用方法

 

4、示例

java">/*** 作用:控制生产者和消费者的执行*/
public class Desk {// 是否有面条:0-没有面条,1-有面条public static int foodFlag = 0;// 总个数public static int count = 10;// 锁对象public static Object lock = new Object();
}
java">/*** 消费者*/
public class Cook extends Thread{@Overridepublic void run() {while (true){synchronized (Desk.lock){if(Desk.count == 0){break;}else {// 核心逻辑// 1、判断桌子上是否有食物if(Desk.foodFlag == 1){// 2、有,等待try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {// 3、没有,制作食物System.out.println("厨师做了一碗面条");// 4、修改桌子上的食物状态Desk.foodFlag = 1;// 5、叫醒等待的消费者开吃Desk.lock.notifyAll();}}}}}
}
java">/*** 生产者*/
public class Foodie extends Thread{/*** 1、循环* 2、同步代码块* 3、判断共享数据是都到了末尾(到了末尾)* 4、判断共享数据是都到了末尾(没到末尾,执行核心逻辑)*/@Overridepublic void run() {while(true){synchronized (Desk.lock){if(Desk.count == 0){// 共享数据到了末尾break;}else {// 执行核心逻辑if(Desk.foodFlag == 0){// 桌子上没有食物, 等待try {// 让当前线程和锁进行绑定Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {// 有面条// 吃的总数-1Desk.count--;System.out.println("吃货正在吃买面条,还能再吃" + Desk.count + "碗!!!");// 吃完唤醒厨师继续做Desk.lock.notifyAll();// 修改桌子的状态Desk.foodFlag = 0;}}}}}
}
java">/*** 测试类*/
public class ProduceDemo {public static void main(String[] args) {// 创建线程对象Foodie foodie = new Foodie();Cook cook = new Cook();// 设置名字foodie.setName("吃货");cook.setName("厨师");// 启动线程foodie.start();cook.start();}
}

 

8-2、阻塞队列

【注意】:

生产者、消费者使用同一个阻塞队列。 

生产者: 

java">public class Cook02 extends Thread{ArrayBlockingQueue<String> blockingQueue;public Cook02(ArrayBlockingQueue<String> blockingQueue){this.blockingQueue = blockingQueue;}@Overridepublic void run() {while (true){try {//底层有锁blockingQueue.put("面条");// 打印语句在锁的外面,控制台输出显示可能有问题,但是不影响数据!System.out.println("厨师放了一碗面");} catch (InterruptedException e) {e.printStackTrace();}}}
}

 消费者:

java">public class Foodie02 extends Thread{ArrayBlockingQueue<String> blockingQueue;public Foodie02(ArrayBlockingQueue<String> blockingQueue){this.blockingQueue = blockingQueue;}@Overridepublic void run() {while (true){String noddle = null;try {//底层有锁noddle = blockingQueue.take();System.out.println(noddle);} catch (InterruptedException e) {e.printStackTrace();}}}
}

测试:

java">public class ProduceQueueDemo {public static void main(String[] args) {ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);Cook02 cook = new Cook02(blockingQueue);Foodie02 food = new Foodie02(blockingQueue);cook.start();food.start();}
}

 

九、多线程的6个状态

 

【注意】:

        没有运行状态,因为一旦线程抢夺到CPU之后,线程就交出去了,JVM就不管了。 

十、内存图

 

main方法运行在main线程中! 

每个线程有一个自己的栈!

堆内存是唯一的! 

示例:

有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
        每次抽的过程中,不打印,抽完时一次性打印(随机)    在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
        分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
        在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
        分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元 

java">public class Test {public static void main(String[] args) {ArrayList<Integer> allPrizes = new ArrayList<>();Collections.addAll(allPrizes, 10, 5, 20, 50, 100, 200, 500, 700, 800, 40, 900, 1000);PrizeDraw p1 = new PrizeDraw(allPrizes);PrizeDraw p2 = new PrizeDraw(allPrizes);PrizeDraw p3 = new PrizeDraw(allPrizes);p1.setName("抽奖箱1");p2.setName("抽奖箱2");p3.setName("抽奖箱3");p1.start();p2.start();p3.start();}
}
java">public class PrizeDraw extends Thread{ArrayList<Integer> allPrizes;public PrizeDraw(ArrayList<Integer> allPrizes){this.allPrizes = allPrizes;}@Overridepublic void run() {ArrayList<Integer> boxList = new ArrayList<>();while (true){synchronized (PrizeDraw.class){if (allPrizes.size() == 0){// 抽完了int max = getMax(boxList);int sum = getSum(boxList);System.out.println(getName() + boxList + "最大奖:" + max + ",总金额:" + sum);break;}else {Collections.shuffle(allPrizes);Integer prize = allPrizes.remove(0);boxList.add(prize);}}}}public int getMax(ArrayList<Integer> list){int max = list.get(0);for (Integer item : list){if(item > max){max = item;}}return max;}public int getSum(ArrayList<Integer> list){int sum = 0;for (Integer item : list){sum = sum + item;}return sum;}
}

http://www.ppmy.cn/server/164750.html

相关文章

自动化、信息化后面是智能化,智能化后面是?

自动化、信息化、智能化是技术进步的逐步发展过程&#xff0c;它们反映了我们在提升效率、优化决策、提高能力方面的持续追求。关于智能化后面是什么&#xff0c;可以从不同角度来探讨未来的发展趋势。自主化&#xff1a;智能化的进一步发展可能会走向自主化。自主系统能在没有…

设计模式-创建型模式-抽象工厂模式

抽象工厂模式简介 抽象工厂模式 &#xff1a; Abstract Factory Pattern 是一种创建型模式。 核心 &#xff1a; “族”的概念&#xff0c;一组具有相同风格或主题的对象&#xff0c;通过同一个工厂接口来创建这个产品族中的对象。 感觉 &#xff1a; 就是对工厂方法模式的一种…

功防世界 Web_php_include

<?php show_source(__FILE__); echo $_GET[hello]; $page$_GET[page]; while (strstr($page, "php://")) {$pagestr_replace("php://", "", $page); } include($page); ?> 代码审计 show_source(__FILE__);&#xff1a; //输出当前文件…

基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发

基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发 摘要 随着人工智能技术的不断发展&#xff0c;语音识别在会议记录、语音笔记等场景中得到了广泛应用。本文介绍了一个基于Python和阿里云百炼大模型的语音识别与文本保存工具的开发过程。该工具能够高效地识别东…

Pdf to forms如何实现?如何在3分钟内将PDF自动转换为Microsoft Forms

通过将杂乱的文件转换为标准化表单&#xff0c;简化数据收集——无需手动操作。 问题&#xff1a;为什么非标准文件会破坏您的工作流程 每天&#xff0c;企业和教育工作者都淹没在非结构化数据中&#xff1a;PDF报告、CSV导出或保存为TXT文件的手写笔记。手动将这些数据复制到…

宝塔安装完redis 如何访问

1&#xff0c;配置bind和密码 我前面在宝塔中安装完成redis&#xff0c;在我的电脑上访问。发现连接不上去。 2&#xff0c;手动杀死一次redis在重启 #执行一下命令 ps -ef | grep 6379 强制杀死进程 125117 是进程号 #杀死进程 kill -9 125117 3&#xff0c;重启redis 重启…

ResNet--深度学习中的革命性网络架构

一、引言 在深度学习的研究和应用中&#xff0c;网络架构的设计始终是一个关键话题。随着计算能力和大数据的不断提升&#xff0c;深度神经网络逐渐成为解决复杂任务的主流方法。然而&#xff0c;随着网络层数的增加&#xff0c;训练深度神经网络往往面临梯度消失或梯度爆炸的…

LeetCode:62.不同路径

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;62.不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &…