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

JavaEE初阶第三期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(一)

专栏:JavaEE初阶起飞计划

个人主页:手握风云

一、认识线程

1.1. 概念

        进程是操作系统进行资源分配和调度的基本单位。简单来说,进程就是程序的一次执行过程。通过进程,用户可以同时运行多个应用程序,提高计算机的利用率和用户体验。同时,进程间的隔离性也增强了系统的稳定性和安全性,一个进程的崩溃通常不会影响到其他进程。

1.2. 为什么要使用线程

        进程虽然说可以实现并发编程的效果,比如写一个服务器来同时处理多个进程,可以进程级别太高,创建或者销毁进程的开销很大。为了优化这些问题,于是引入了线程。

1.3. 进程和线程的关系

        进程包含线程,线程也可以被称为轻量级进程。每个线程都可以独立执行一段逻辑,并且独立在CPU上调度;同一个进程中的多个线程,共享进程的资源,如内存资源、文件描述符表等。当进程已经有了,在进程内部在创建新线程,就把申请资源开销省下来了。

1.4. 多线程模型

        我们假设有一个滑稽老铁去吃100只烧鸡,花费的时间肯定会太长。按照多进程模型,让两个滑稽老铁一人分别吃50只,虽然效率提升了,但我们需要额外申请内存空间。

        但我们如果按照多线程的模型来,让两个滑稽老铁在同一个桌子上来分别吃50只烧鸡,省下了申请资源的开销,效率就会进一步提高。

        但线程并不是越多,效率就越高。当滑稽老铁的数目进一步增多,但桌子就这么大,有可能其中一人吃得好好的,会被其他人挤走。此时非但不会提高效率,效率反而越低了,那此时就只能对机器进行性能优化。

        当线程数目比较少时,也会出现一些问题:

  • 当两个线程尝试操作一个共享的资源时,比如内存中的同一个变量,就可能会发生冲突,从而引起bug。
  • 如果某个线程抛出异常,且没有其他代码把这个异常catch住,就会导致进程内的所有线程会被随之带走,整个进程也会结束。

二、多线程的创建

        多线程编程和多进程编程都是操作系统提供的API,因为Java是一次编译到处运行的,JVM已经把系统差异给屏蔽了,JVM已经把多线程的API进行了较好地封装,但多进程的API封装得比较粗糙。下面就通过代码来感受一下Java中如何使用多线程。

2.1. 继承Thread类

public class Demo1 {
// 定义一个静态内部类MyThread,继承自Thread类static class MyThread extends Thread {// 重写run方法@Overridepublic void run() {System.out.println("hello thread");}}public static void main(String[] args) {// 创建一个MyThread对象Thread thread = new MyThread();// 启动线程thread.start();}
}

        Thread类是Java中的核心类,用于表示和管理线程。由于操作系统本身就提供了一组操作线程的函数,但这个函数是C语言版本的。JVM把这些操作系统的函数给封装成了Java版本,到了程序员手中,就成了Thread。使用Thread类,就相当于在使用操作系统的API。run方法是线程执行体,包含了线程需要执行的任务代码。当线程启动时,run方法中的代码将在新的线程中执行,也就是多线程程序的入口。

        在MyThread类里面重写的是run()方法,但在main()方法里调用的却是start()方法,这是因为start()方法是调用系统API,真正在操作系统内部创建一个线程,这个新的线程就会以run()作为入口,执行里面的逻辑,而run()方法就不需要在代码中显式调用。

        但如果我们调用run()方法,虽然也能打印"hello thread",但没有创建新的线程,而是直接在main()方法所在的 “主线程” 里执行了run()方法的逻辑。对于一个进程,至少得有一个线程;对于一个Java程序来说,main()方法,所在的进程至少会有这个线程,这个线程就是主线程。上面的代码就具有一个主线程和一个新创建的线程。

  • sleep静态方法

        Thread.sleep是Thread类中的一个静态方法。当调用这个方法时,当前线程会被阻塞,不执行任何操作,直到指定的毫秒数过去。这个方法可以用于多种场景,比如在循环中添加延迟,或者在执行某些操作之前等待一段时间。

public class MyThread extends Thread {@Overridepublic void run() {System.out.println("Hello Thread!");}
}
public class Demo2 {public static void main(String[] args) {// 创建一个MyThread对象Thread thread = new MyThread();// 启动线程thread.start();while (true) {System.out.println("Hello Main!");// 休眠1000秒Thread.sleep(1000);}}
}

        但此时的代码会显示出错误,原因是我们有受查的异常,必须进行显式处理,可以用throws抛出或者try{}catch{}进行捕获。

//throws
public class Demo2 {public static void main(String[] args) throws InterruptedException {// 创建一个MyThread对象Thread thread = new MyThread();// 启动线程thread.start();while (true) {System.out.println("Hello Main!");// 休眠1000秒Thread.sleep(1000);}}
}//try{}catch{}
public class Demo2 {public static void main(String[] args) {// 创建一个MyThread对象Thread thread = new MyThread();// 启动线程thread.start();while (true) {System.out.println("Hello Main!");// 休眠1000秒try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

        同理,我们在MyTread类里面进行循环打印。但这里只能使用try{}catch{}进行捕获,因为run()方法是重写自父类的方法,如果使用throws抛出不符合父类方法的声明。

        完整代码实现:

public class MyThread extends Thread {@Overridepublic void run() {while (true) {System.out.println("Hello Thread!");try {// 休眠1秒Thread.sleep(1000);} catch (InterruptedException e) {// 抛出运行时异常throw new RuntimeException(e);}}}
}
public class Demo2 {public static void main(String[] args) throws InterruptedException {// 创建一个MyThread对象Thread thread = new MyThread();// 启动线程thread.start();while (true) {System.out.println("Hello Main!");// 休眠1000秒Thread.sleep(1000);}}
}

        运行结果如下,虽然两个打印分别在不同的while循环中,两个线程属于并发关系,独立在CPU上执行。我们同时也可以看到两个线程不是严格交替执行的,由于两个线程都加了休眠,当1000毫秒到后,哪个线程线程的执行顺序是无法确定的,这是因为操作系统调度的线程是随机不可预测的。

        我们还有更直观的办法来观察多线程:在我们安装的JDK里面的bin目录下,以管理员身份运行jconsole.exe,然后点击连接本地进程Demo2,然后点击线程,就可以看到我们的Thread-0和main两个线程,剩下的线程都是JVM自带的,这些线程进行了一些背后的操作,比如垃圾回收、记录统计信息、记录一些调试信息等。

2.2. 实现Runnable接口

public class Demo3 {public static void main(String[] args) throws InterruptedException {// 创建一个实现了Runnable接口的实例Runnable myRunnable = new MyRunnable();// Runnable本身没有start方法,需要配合Thread类来使用// 创建一个Thread对象,并将myRunnable作为参数传入Thread thread = new Thread(myRunnable);// 启动线程thread.start();while (true) {System.out.println("Hello Main!");Thread.sleep(1000);}}
}class MyRunnable implements Runnable {@Overridepublic void run() {while (true) {System.out.println("Hello Tread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

        运行结果:

        对于第一种继承Thread写法,描述任务的时候,代码是写到Thread子类中的,意味着任务内容与Thread类耦合度高,未来想把这个任务给别的主体来执行。

        对于第二种Runnable接口写法,任务是写到Runnable中,不涉及到任何和“线程”的概念,任务内容和Thread耦合度几乎没有,后序可以把这个任务交给进程或者协程来执行。

2.3. 匿名内部类

public class Demo4 {public static void main(String[] args) {// 创建一个新的线程Thread thread = new Thread() {@Overridepublic void run() {while (true) {System.out.println("Hello Tread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};// 启动线程thread.start();while (true) {// 在主线程中输出"Hello Main!"System.out.println("Hello Main!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

        此处创建了没有的名字匿名内部类,并且这个类是Thread类的子类,该子类也重写了run()方法,也创建了子类实例,通过thread引用指向子类。这样写可以简化代码。

        运行结果如下:

2.4. lambda表达式

public class Demo5 {public static void main(String[] args) {// 创建一个新的线程Thread thread = new Thread(() -> {while (true) {System.out.println("Hello Thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程thread.start();// 无限循环while (true) {System.out.println("Hello Main!");try {// 主线程休眠1秒Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

        运行结果如下:

http://www.lryc.cn/news/573397.html

相关文章:

  • 【开源项目】当大模型推理遇上“性能刺客”:LMCache 实测手记
  • linux安装minio并使用
  • 在Docker、KVM、K8S常见主要命令以及在Centos7.9中部署的关键步骤学习备存
  • XCUITest + Objective-C 详细示例
  • FastGPT:开启大模型应用新时代(4/6)
  • Springboot 配置 FastJson 替换 Jackson
  • Rabbitmq集成springboot,手动确认消息basicAck、basicNack、basicReject的使用
  • 在 MyBatis 的xml中,什么时候大于号和小于号可以不用转义
  • Axios 在 Vue3 项目中的使用:从安装到组件中的使用
  • 升级到 .NET 9 分步指南
  • “最浅”的陷阱:聊聊二叉树最小深度的递归坑点与解法哲学
  • 秋招Day14 - MySQL - SQL优化
  • c++11标准(5)——并发库(互斥锁)
  • 一、什么是生成式人工智能
  • 终端里的AI黑魔法:OpenCode深度体验与架构揭秘
  • Java ArrayList集合和HashSet集合详解
  • 【论文笔记】【强化微调】TinyLLaVA-Video-R1:小参数模型也能视频推理
  • 人人都是音乐家?腾讯开源音乐生成大模型SongGeneration
  • 旧物回收小程序开发:开启绿色生活新方式
  • Python列表常用操作方法
  • 从语义到推荐:大语言模型(LLM)如何驱动智能选车系统?
  • 首页实现多级缓存
  • AWS-SAA 第二部份:安全性和权限管理
  • 《map和set的使用介绍》
  • Linux TCP/IP协议栈中的TCP输入处理:net/ipv4/tcp_input.c解析
  • TCP 三次握手与四次挥手全流程详解
  • 【智能体】n8n聊天获取链接后爬虫知乎
  • 设计模式精讲 Day 9:装饰器模式(Decorator Pattern)
  • 【RTP】基于mediasoup的RtpPacket的H.264打包、解包和demo 1:不含扩展
  • 2D曲线点云平滑去噪