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

Java多线程介绍及使用指南

“多线程”:并发

要介绍线程,首先要区分开程序、进程和线程这三者的区别。

程序:具有一定功能的代码的集合,但是是静态的,没有启动运行
进程:启动运行的程序【资源的分配单位】
线程:进程中的每一条执行路径,就是线程。

概念
并行:多个CPU同时执行多个任务
并发:一个CPU“同时”执行多个任务(采用时间片切换)

将1分钟---分成10000份=6毫秒

5.1使用线程的3种方式:

第一种方式:继承父类

1.创建线程类:
public class NumberThread extends Thread
2.创建线程对象:【新生状态】
NumberThread num=new NumberThread();
3.【就绪状态】--->cpu给资源--->运行状态
num.start();
如果调用run(),则不是多线程的了,会直接执行完

例子:售票窗口
有10张车票,3个窗口,同时售票,显示售票结果。

package thread;  public class WindowThread extends Thread {  static int ticket=10;  public WindowThread() {  }  public WindowThread(String name) {  super(name);  }  @Override  public void run() {  while (ticket >= 1) {  System.out.println(getName()+"窗口,卖出第"+ticket+"票");  ticket--;  }  }  
}
package thread;  public class TestMain {  public static void main(String[] args) {  WindowThread w1=new WindowThread("第1");  w1.start();  WindowThread w2=new WindowThread("第2");  w2.start();  WindowThread w3=new WindowThread("第3");  w3.start();  }
}

在这里插入图片描述

优点:启动线程对象高效率
缺点:占用了父类位置

第二种方式:实现接口

public class Window implements Runnable {  int ticket=10;  @Override  public void run() {  while (ticket >= 1) {  System.out.println(Thread.currentThread().getName()+"窗口卖出第"+ticket+"张票");  ticket--;  }  }  
}
public class Test {  public static void main(String[] args) {  Window w1=new Window();  Thread t1 = new Thread(w1,"第1");  t1.start();  Thread t2 = new Thread(w1, "第2");  t2.start();  Thread t3 = new Thread(w1, "第3");  t3.start();  }  
}

优点:没有占用父类位置,共享资源能力强,资源不用加static
缺点:启动线程对象 效率低

第三种方式:实现接口

对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法
但是这个run方法有不足:

在这里插入图片描述

1)没有返回值
2)不能抛出异常

基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口:

实现Callable接口好处:(1)有返回值 (2)能抛出异常
缺点:线程创建比较麻烦

package com.msb.test05;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*** @author : msb-zhaoss*/
public class TestRandomNum implements Callable<Integer> {/*1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型2.如果带泛型,那么call的返回值就是泛型对应的类型3.从call方法看到:方法有返回值,可以跑出异常*/@Overridepublic Integer call() throws Exception {return new Random().nextInt(10);//返回10以内的随机数}
}
class Test{//这是main方法,程序的入口public static void main(String[] args) throws ExecutionException, InterruptedException {//定义一个线程对象:TestRandomNum trn = new TestRandomNum();FutureTask ft = new FutureTask(trn);Thread t = new Thread(ft);t.start();//获取线程得到的返回值:Object obj = ft.get();System.out.println(obj);}
}

FutureTaskRunnable接口的一个实现类,因此它可以作为参数传给Tread
用线程任务对象来接返回值(这里用的是ft)
注意:get方法的使用位置必须在start之后
创建几个线程对象,就要创建几个FutreTask,而不能new两个Thread,因为这样是一个对象的内容接了两遍

5.2线程对象的常用方法:

  • a.getName()获得当前线程对象的名字:
    ---线程名 如果开发者使用了无参构造器,程序自动设置线程名Thread-0++(主线程除外)
    ---开发者使用有参构造器,参数的值就是线程的名字。
public class NumberThread extends Thread{  public NumberThread() {  }  public NumberThread(String name) {  super(name);  }  @Override  public void run() {    //第二条执行路线的内容  for (int i = 1; i < 100; i++) {  System.out.println(super.getName()+"i="+i);   //获得当前线程名  }  }  
}

---借助num.setName("线程1"),为线程名赋值

public class TestMain {  //main()方法所在的线程叫主线程  public static void main(String[] args) {  NumberThread num=new NumberThread("窗口1");  //此时程序开启第二条路  num.start();  NumberThread num2=new NumberThread("窗口2");  //此时程序开启第二条路  num2.start();  for (int i = 1; i < 100; i++) {  System.out.println(Thread.currentThread().getName()+"i="+i);  }  }  
}
  • b.currentThread()获得当前正在运行的线程对象
    针对实现接口的情况,由于没有再继承Thread类,因此也无法直接使用其中的方法getName(),但Thread类中有静态方法currentThread(),因此可以通过这种方法获得当前运行的线程对象的信息。
  • c.setPriority()设置线程的优先级,1-10之间,默认是5
Thread t2 = new Thread(w1, "第2");  
t2.setPriority(1);  //设置线程的优先级,1-10之间,默认是5  
t2.start();
  • d.Thread.currentThread().stop() 过期方法,不建议执行
  • e.强行占用。当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。
public static void main(String[] args) throws InterruptedException {  NumberThread num=new NumberThread("窗口1");  //此时程序开启第二条路  num.start();  for (int i = 1; i < 100; i++) {  System.out.println(Thread.currentThread().getName()+"i="+i);  if (i == 50) {  num.join();  //强势加入(num运行完了后,其余的才能执行)System.out.println("maini=50");  }  }  
}
  • f.setDaemon(true);伴随线程
    tt.setDaemon(true);//设置伴随线程
    主线程死亡,伴随线程也会跟着一起死亡。
public class TestThread extends Thread {@Overridepublic void run() {for (int i = 1; i <= 1000 ; i++) {System.out.println("子线程----"+i);}}
}
class Test{//这是main方法,程序的入口public static void main(String[] args) {//创建并启动子线程:TestThread tt = new TestThread();tt.setDaemon(true);//设置伴随线程  注意:先设置,再启动tt.start();//主线程中还要输出1-10的数字:for (int i = 1; i <= 10 ; i++) {System.out.println("main---"+i);}}
}
  • g.sleep(毫秒):设置线程休眠
public static void main(String[] args) throws InterruptedException {  System.out.println("1111");  Thread.sleep(1000);  //休眠的例子System.out.println("222");  
}

5.3线程的生命周期:

使用线程构造器---创建线程对象--->线程新生状态
创建线程对象.start()--->进入到就绪状态【有资格,没资源】
线程对象.run()--->进入到运行状态【有资格,有资源】

在时间片段内,执行完--->死亡状态
在时间片段内,没执行完--->重回就绪状态
在时间片段内,出现突发事件--->阻塞状态--->就绪状态

在这里插入图片描述

5.4解决线程安全问题:

【第一种:同步代码块】

public void run() {  while (true) {  synchronized (WindowThread.class){ //WindowThread.class是监视器对象  if(ticket >= 1) {  System.out.println(getName() + "窗口,卖出第" + ticket + "票");  ticket--;  }  }  }  
}

总结:
同步监视器总结:
1:认识同步监视器(锁子) ----- synchronized(同步监视器){ }
1)必须是引用数据类型,不能是基本数据类型
2)也可以创建一个专门的同步监视器,没有任何业务含义
3)一般使用共享资源做同步监视器即可
4)在同步代码块中不能改变同步监视器对象的引用
5)尽量不要String和包装类Integer做同步监视器
6)建议使用final修饰同步监视器

2:同步代码块的执行过程
1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open
3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态
4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open
5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)
强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close)

3:其他
1)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块
2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块

【第二种:同步方法】

public class WindowThread extends Thread {  static int ticket = 300;  public WindowThread() {  }  public WindowThread(String name) {  super(name);  }
@Override  
public void run() {  while (true) {  if (ticket == 0) {  break;  } else {  buyTicket();  }   }  
}  public static synchronized void buyTicket(){   
//如果只有synchronized,锁住的只是当前this对象(相当于每一个都有300张票)
//如果加了static,锁住的就是方法。if(ticket >= 1) {  System.out.println(Thread.currentThread().getName() + "窗口,卖出第" + ticket + "票");  ticket--;  }  }
}

总结:
1:
多线程在争抢资源,就要实现线程的同步(就要进行加锁,并且这个锁必须是共享的,必须是唯一的。
咱们的锁一般都是引用数据类型的。
目的:解决了线程安全问题。

2:关于同步方法

  1. 不要将run()定义为同步方法
  2. 非静态同步方法的同步监视器是this
    静态同步方法的同步监视器是 类名.class 字节码信息对象
  3. 同步代码块的效率要高于同步方法
    原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部
  4. 同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块
    【第三种方式:Lock锁】
@Override  //Lock锁  
public void run() {  Lock lock = new ReentrantLock();  //创建Lock锁对象  while (true) {  lock.lock();//上锁  if (ticket >= 1) {  System.out.println(getName() + "窗口,卖出第" + ticket + "票");  ticket--;  }  lock.unlock();  //解锁  if (ticket == 0) {  break;  }  }  
}

5.5线程通信:

在这里插入图片描述

package com.msb.test11;
/*** @author : msb-zhaoss*/
public class Product {//商品类//品牌private String brand;//名字private String name;//引入一个灯:true:红色  false 绿色boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费//setter,getter方法;public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public String getName() {return name;}public void setName(String name) {this.name = name;}//生产商品public synchronized void setProduct(String brand,String name){if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费try {wait();} catch (InterruptedException e) {e.printStackTrace();}}//灯是绿色的,就生产:this.setBrand(brand);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.setName(name);//将生产信息做一个打印:System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());//生产完以后,灯变色:变成红色:flag = true;//告诉消费者赶紧来消费:notify();}//消费商品:public synchronized void getProduct(){if(!flag){//flag == false没有商品,等待生产者生产:try {wait();} catch (InterruptedException e) {e.printStackTrace();}}//有商品,消费:System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());//消费完:灯变色:flag = false;//通知生产者生产:notify();}
}

相关文章:

Java多线程介绍及使用指南

“多线程”&#xff1a;并发 要介绍线程&#xff0c;首先要区分开程序、进程和线程这三者的区别。 程序&#xff1a;具有一定功能的代码的集合&#xff0c;但是是静态的&#xff0c;没有启动运行 进程&#xff1a;启动运行的程序【资源的分配单位】 线程&#xff1a;进程中的…...

HarmonyOS 5.0应用开发——列表(List)

【高心星出品】 文章目录 列表&#xff08;List&#xff09;列表介绍列表布局设置主轴方向设置交叉轴方向 列表填充分组列表填充 滚动条位置设置滚动位置滚到监听 列表项侧滑 列表&#xff08;List&#xff09; 列表介绍 列表作为一种容器&#xff0c;会自动按其滚动方向排列…...

自动化电气行业的优势和劣势是什么

优势 市场需求广泛&#xff1a; 自动化电气技术广泛应用于电力系统、制造业、交通、农业等多个领域&#xff0c;随着智能化、数字化趋势的加强&#xff0c;其市场需求持续增长。在智能制造、智能电网等领域&#xff0c;自动化电气技术更是发挥着关键作用&#xff0c;推动了行业…...

第 42 章 - Go语言 设计模式

在Go语言中&#xff0c;设计模式是一种被广泛接受的解决常见问题的最佳实践。这些模式可以分为三类&#xff1a;创建型模式、结构型模式和行为型模式。下面我将结合案例以及源代码对这三种类型的设计模式进行详细讲解。 创建型模式 创建型模式主要关注对象的创建过程&#xf…...

【机器学习】---大语言模型

引言&#xff1a;开启大语言模型的奇幻旅程 近年来&#xff0c;人工智能&#xff08;AI&#xff09;领域正在经历一场前所未有的技术革命&#xff0c;而其中最耀眼的明星莫过于大语言模型&#xff08;Large Language Models, LLMs&#xff09;。这些模型&#xff0c;犹如现代科…...

挑战用React封装100个组件【002】

项目地址 https://github.com/hismeyy/react-component-100 组件描述 组件适用于需要展示图文信息的场景&#xff0c;比如产品介绍、用户卡片或任何带有标题、描述和可选图片的内容展示 样式展示 代码展示 InfoCard.tsx import ./InfoCard.cssinterface InfoCardProps {t…...

MarkDown-插入图片-图片url地址的生成获取方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、url地址是什么二、如何获取图片的url地址1.了解图床2.使用图床获取图片URL地址2.1进入网站后&#xff0c;点击右下角“Select Image.”按钮&#xff0c;即可…...

插值、拟合和回归分析的相关知识

目录 0 序言 1 分段线性插值 2 多项式插值 3 样条插值 4 最小二乘拟合 5 多元线性回归 0 序言 在生产实践和科学研究中&#xff0c;常常有这些问题: 插值问题&#xff1a;由实验或测量得到变量间的一批离散样点&#xff0c;要求得到变量之间的函数关系或得到样点之外的…...

【小白学机器学习42】进行多次抽样,样本的分布参数和总体的分布参数的关系

目录 1 进行多次抽样&#xff0c;样本的分布参数和总体的分布参数的关系 2 样本容量越大&#xff0c;多次抽样的样本的分布参数和总体的分布参数的关系 3 随着样本容量增大&#xff0c;多次抽样均值的 平均值&#xff0c;方差的变化 4 随着样本容量增大&#xff0c;多次抽…...

链动星海 质引未来|中信银行加码科技金融 “接力式”服务助力“新质生产力”释放

11月26日&#xff0c;第二届中国国际供应链促进博览会&#xff08;以下简称链博会&#xff09;在北京中国国际展览中心开幕。中信集团以“链动星海 质引未来”为主题&#xff0c;亮相先进制造链展区。此次布展由中信金控主办、中信银行承办&#xff0c;携手中信证券、中信建投证…...

黑马2024AI+JavaWeb开发入门Day02-JS-VUE飞书作业

视频地址&#xff1a;哔哩哔哩 讲义作业飞书地址&#xff1a;飞书 一、作业1 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge">&l…...

云计算基础-期末复习

第一章&#xff1a;云计算概论 一、云计算的定义与特征 1. 定义&#xff1a; 云计算是一种通过网络以按需、可扩展的方式获取计算资源和服务的模式。它将计算资源视为一种公用事业&#xff0c;用户可以根据需求动态获取和释放资源&#xff0c;而无需了解底层基础设施的细节。…...

Java GET请求 请求参数在Body中使用Json格式传参

业务需要调个三方接口 使用GET请求方式 但是&#xff01;请求参数不在Query中&#xff0c;竟然在Body中&#xff0c;使用Json格式传参 在API调试工具里面可以调通 在java代码里&#xff0c;死活调不通 网上搜了搜&#xff0c;找到一个靠谱的&#xff0c;记录一下 import o…...

AI数据分析工具(一)

Looker Studio&#xff08;谷歌&#xff09;-免费 优点 免费使用&#xff1a;对于中小型企业和个人用户来说&#xff0c;没有任何费用压力&#xff0c;可以免费享受到数据可视化和报表创建的功能。与Google服务集成&#xff1a;特别适合使用Google产品生态的企业&#xff0c;…...

go结构体匿名“继承“方法冲突时继承优先顺序

在 Go 语言中&#xff0c;匿名字段&#xff08;也称为嵌入字段&#xff09;可以用来实现继承的效果。当你在一个结构体中匿名嵌入另一个结构体时&#xff0c;嵌入结构体的方法会被提升到外部结构体中。这意味着你可以直接通过外部结构体调用嵌入结构体的方法。 如果多个嵌入结…...

【049】基于51单片机语音录放【Proteus仿真+Keil程序+报告+原理图】

☆、设计硬件组成&#xff1a;51单片机最小系统ISD4004语音芯片LM386音频放大器喇叭LCD1602液晶显示按键控制LED灯。 1、本设计采用STC89C51/52、AT89C51/52、AT89S51/52作为主控芯片&#xff0c;LCD1602液晶显示屏实时显示&#xff1b; 2、系统具有两种模式&#xff1a;录音…...

《软件项目管理》期末-复习题及参考答案

&#xff08;1&#xff09;赶工一个任务时&#xff0c;你应该关注&#xff08; C &#xff09; A. 尽可能多的任务 B. 非关键任务 C. 加速执行关键路径上的任务 D. 通过成本最低化加速执行任务 &#xff08;2&#xff09;下列哪个不是项目管理计划的一部分&#xff1f;&#x…...

milvus 通俗易懂原理

向量值如何生成的 Milvus 是一个开源的向量数据库&#xff0c;专门用于处理高维向量的存储、搜索和分析。向量值本身通常来自于某些机器学习或深度学习模型的输出&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;、计算机视觉&#xff08;CV&#xff09;、推荐系…...

什么是撞库、拖库和洗库?

“撞库”是黑客通过收集互联网已泄露的用户和密码信息&#xff0c;生成对应的字典表&#xff0c;尝试批量登陆其他网站后&#xff0c;得到一系列可以登录的用户。 很多用户在不同网站使用的是相同的帐号密码&#xff0c;因此黑客可以通过获取用户在A网站的账户从而尝试登录B网…...

安卓-碎片的使用入门

1.碎片(Fragment)是什么 Fragment是依赖于Activity的&#xff0c;不能独立存在的,是Activity界面中的一部分&#xff0c;可理解为模块化的Activity,它能让程序更加合理和充分地利用大屏幕的空间&#xff0c;因而在平板上应用得非常广泛. Fragment不能独立存在&#xff0c;必须…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

莫兰迪高级灰总结计划简约商务通用PPT模版

莫兰迪高级灰总结计划简约商务通用PPT模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...