Java EE 初阶:线程(1)

embedded/2024/12/21 8:51:37/

线程的初步认识

1.什么是线程

一个线程就是一个“执行流”,每个进程之间都可以按照顺序执行自己的代码,多个线程之间同时执行着多份代码。

2.进程和线程的关系

进程我们可以理解为是资源分配的基本单位,是一个程序的运行实例。

线程我们可以理解为是进程中的执行单元,是CPU调度的调度的基本单位,一个进程中可以包含多个线程(在进程管辖内的线程),这些线程可以共享进程中的资源。

进程和线程所涉及的资源都是独立的,不会相互影响,具有稳定性。

  • CPU,内存,硬盘资源(文件描述符)网络宽带等都属于进程。
  • 进程内部管辖着多个线程之间,会共享上述的内存资源和硬盘资源,网络宽带等......

一个进程至少包括一个线程。

进程是操作系统的基本单位

3.进程和线程的资源申请和释放

对于进程来说,进程的创建和销毁都需要申请和释放资源。

对于线程来说,进程的创建,只需要第一次创建时申请资源,以后的创建都不需要申请资源。只有将全部的线程销毁才会释放资源,只销毁一个或者一部分不会释放资源。

4.线程是CPU调度执行的基本单位

如果一个进程有多个线程,这些线程各自去CPU上调度执行,可能会出现多种情况。

比如:有线程1,2,3

  • 线程1去CPU核心1去执行,线程2去核心2去执行,线程3去核心3去执行。
  • 可能线程1,2,3在一个核心上来回的切换
  • 也可能线程1,2在核心1上来回切换,线程3和别的线程在其他的核心上来回的切换。

5.线程的调度相关的数据(每一个线程都有这些数据)

描述线程的基本属性:

  • 线程 ID(Thread ID):线程的唯一标识。
  • 优先级(Priority):线程的调度优先级,通常用整数表示。
  • 线程状态(Thread State):如就绪、运行、阻塞、终止等。
  • 所属进程 ID(Parent Process ID):线程所在的进程标识。

 6.线程的效率

比如有一个人吃100个面包,需要100分钟。

如果我们增加一个人,两个人同时吃100个面包,时间减少一半,需要50分钟。

那我们再添加两个人,四个人同时吃面包,时间还能再减少一半,需要25分钟,效率大大提高。

如果我们200个人吃100个面包呢,200个人则会对100面包进行争抢,大大的降低效率。

所以适当的线程的虽然会提高效率但是线程太多的话,线程的调度开销也是十分的明显,因为线程的调度开销降低程序的性能。

如果在吃的过程中,两个人看中了同一个面包,则会产生冲突,则会产生线程不安全的问题,这样的问题会是代码出现bug。

一个线程抛出异常,可能会带走整个进程,使所有的线程无法工作

服务器

服务器这个词不一定指的是服务器的软件(程序),也可能指的是部署了服务器软件的这一台机器(硬件)。

一个服务器可以给多个客户端提供服务,也可以只给一个客户端提供服务。

Thread类(标准库中提供的类)

线程是操作系统给出的概念,操作系统提供了一些Api给程序员使用

Api(Application programming interface)->别人写了一些类和函数,你拿过直接能用

操作系统提供的原生线程Api是由C语言的,不同操作系统线程Api不同。但是通过Java的统一封装,封装成了Thread类。

创建一个多线程的程序

java">class MyThread extends Thread{public void run() {while (true) {System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
public class demo1 {public static void main(String[] args) throws InterruptedException {Thread t=new MyThread();t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}

注解:

我们发现在创建对象时,不需要import包,就可以直接的使用Thread,这是因为有些包可以不用导入就直接使用,java.long默认import 

使用了向上转型

通过start创建一个线程,多了一个执行流。

真正在系统中创建线程,(JVM调用系统的API~来完成创建线程的操作)

让当前的线程暂时的休息一会,等到1000毫秒之后,继续执行。

运行的结果

一直会重复的运行

我们可以通过第三方的工具来查看线程的详细情况

1.找到program files

2.打开java文件找到bin文件

3.找到jconsole

4.进行连接

5.查看线程

创建Thread的方式

1.继承Thread,重写run

java">class MyThread extends Thread{public void run() {while (true) {System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
public class demo1 {public static void main(String[] args) throws InterruptedException {Thread t=new MyThread();t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}

run方法相当于回调函数

回调函数是指将一个函数作为参数传递给另一个函数,在某些特定事件或条件满足时,由后者调用这个函数,回调函数本质上是一个函数,但它被传递到另一个函数中以便后续调用

2.实现Runnable,重写run

java">class myRunnable implements Runnable{@Overridepublic void run() {while (true) {System.out.println("myRunnable");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class demo2 {public static void main(String[] args) throws InterruptedException {Runnable runnable=new myRunnable();Thread t=new Thread(runnable);t.start();while (true){System.out.println("demo1");Thread.sleep(1000);}}
}

表示一个“可以执行的任务”,这种写法可以更好地进行解耦合(高内聚低耦合)

 3.使用匿名内部类

不知道名字的内部类,叫做匿名内部类

java">public class demo3 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(){public void run(){while (true) {System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}

如果一段代码是一次性的,可以使用匿名内部类来进行定义

4.使用Runnable,匿名内部类

java">public class demo4 {public static void main(String[] args) throws InterruptedException {Runnable runnable=new Runnable() {@Overridepublic void run() {while (true) {System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};Thread t=new Thread(runnable);t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}

5.引用ambda表达式

本质上是匿名函数,最主要的用途是作为回调函数

java">public class demo5 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (true){System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}

Thread类其他的属性和办法

Thread()使用这个写法,必须重写Thread中的run

Thread(Runnable target)使用Runnable对象创建线程对象,不用重写Thread中的run

Thread(Runnable target,String name)使用这个方法可以给线程起名字

java">public class demo6 {public static void main(String[] args) {Thread t1=new Thread(()->{while (true){System.out.println("thread  1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"t1");t1.start();Thread t2=new Thread(()->{while (true){System.out.println("thread  2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"t2");t2.start();Thread t3=new Thread(()->{while (true){System.out.println("thread  3");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"t3");t3.start();}
}

 

Thread中几个常见的属性

java">public class Demo7 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (true){System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.setDaemon(true);t.start();for (int i = 0; i < 3; i++) {System.out.println("start");Thread.sleep(1000);}System.out.println("结束");}
}

 得放在start之前

前台线程:是程序的主要工作线程,它的存在会阻止应用程序的终止。只要有任何前台线程仍在运行,整个应用程序就会继续运行。 

后台线程:是辅助性线程,不会阻止应用程序的终止。如果所有前台线程结束,应用程序将立即终止,无论后台线程是否完成。

这些是JVM自带的线程

java">public class Demo8 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{for (int i = 0; i < 3; i++) {System.out.println("Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.isAlive();t.start();while (true){System.out.println(t.isAlive());Thread.sleep(1000);}}
}

 运行结果:

 

注:此时的运行结果一定是false,因为还没有start,还没有创建线程

启动一个线程

start()

start()是java标准库的方法,JVM提供的,本质上是调用操作系统的API

查看start()的源码

注:1.带着native的方法,是在JVM内部的方法,是由c++实现的,调用了其方法,所以无法看到,如果想看到,必须额外下载其对应的源码。

2.每个Thread只能start一次,跟日抛的意思差不多,

中断一个线程

线程的入口执行完毕,自然就会中断

通过增加条件来判断是否完成,来进行中断。

java">public class Demo10 {private static boolean isFinished = false;public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!isFinished){System.out.println("Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("结束");});t.start();Thread.sleep(3000);isFinished=true;}
}

 

如果把private static boolean isFinished = false;这个成员变量改为局部变量还可以吗?

不可以

因为lambda 是回调函数,执行时机,是很久之后(操作系统真正创建出线程之后,才会执行的)
很有可能,后续线程创建好了,当前 main 这里的方法都执行完了,对应的 isfinished 就销毁了....

为了解决上述的问题,
Java 的做法是,把被捕获的变量给拷贝一份,拷贝给 lambda 里面
外面的变量是否销毁,就不影响 lambda 里面的执行了

拷贝,意味着, 这样的变量就不适合进行修改
修改一方,另一方不会随之变化的(本质上是两个变量)
这种一边变,一边不变,可能给程序员带来更多的困惑
Java 大佬们想了个办法,压根不允许你这里进行修改

如果是成员变量

lambda 本质上是 函数式 接口
相当于一个 内部类
isFinished 变量本身就是外部类的成员
内部类本来就能够访问外部类的成员,所以定义为成员变量的可以

成员变量是由GC(垃圾回收 Garbage Collection)管理的,在lambda中,不用担心生命周期失效的问题,由于GC 搞的太好,让咱们写代码的时候,不太能感知到生命周期的问题。

线程终止

线程的Thread中提供了对象,所以不需要自己写,直接用就可以了

java">public class Demo11 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {break;}}System.out.println("结束");});t.start();Thread.sleep(3000);System.out.println("main 线程尝试终止 t 线程");t.interrupt();}
}

通过 while (!Thread.currentThread().isInterrupted()) 检查中断状态决定是否继续执行。但当线程调用 Thread.sleep() 时,被中断后抛出的 InterruptedException 会自动清除中断标志,导致 isInterrupted() 始终返回 false。 

改进后:

java">public class Demo11 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {break;}}System.out.println("结束");});t.start();Thread.sleep(3000);System.out.println("main 线程尝试终止 t 线程");t.interrupt();}
}

 希望能对大家有所帮助!!!!

 


http://www.ppmy.cn/embedded/147487.html

相关文章

双臂机器人

目录 一、双臂机器人简介 二、双臂机器人系统的组成 三、双臂机器人面临的主要挑战 3.1 协调与协同控制问题 3.2 力控制与柔顺性问题 3.3 路径规划与轨迹优化问题 3.4 感知与环境交互 3.5 人机协作问题 3.6 能源与效率问题 3.7 稳定性与可靠性问题 四、双臂机器人…

域名和服务器是什么?域名和服务器是什么关系?

在互联网的生态系统中&#xff0c;域名和服务器是两个至关重要的组成部分。它们共同构成了我们访问网站和使用在线服务的基础。那么域名和服务器是什么?域名和服务器是什么关系? 1、域名的概念 域名是互联网中用于标识特定地址的一种文字形式。它是用户访问网站时输入的易记…

慢牛提速经典K线形态-突破下跌起始位和回档三五线,以及徐徐上升三种形态

龙头股在缓慢的上升过程中&#xff0c;多已小阳线&#xff0c;小阴线收盘&#xff0c;很少出现特别经典的K线形态&#xff0c;但在临近启动前的打压环节&#xff0c;可能会出现一些特别恶劣的K线形态&#xff0c;其目的无外乎吓退散户。 一、突破下跌起始位启动 突破下跌起始位…

镍镁合金冶金轧辊的优异性能与制造工艺

镍镁合金冶金轧辊因其独特的性能优势&#xff0c;在冶金工业中发挥着重要作用。以下是对其优异性能与制造工艺的详细探讨&#xff1a; 一、优异性能 高强度与轻质化&#xff1a; 镍镁合金冶金轧辊通过合理的成分设计和热处理工艺&#xff0c;能够在保持高强度的同时&#xff0c…

多核CPU调度是咋搞的?

其实很多情况下都有 这样的疑问 为什么多核CPU用着用着会“躺平”&#xff1f; 为什么手机有 8 核&#xff0c;跑分时性能却不是核心数的翻倍&#xff1f; 答案的钥匙&#xff0c;就藏在多核CPU的调度机制里。 为了更直观地理解&#xff0c;以一个《王者荣耀》游戏服务器为例…

线性代数基础与应用:基底 (Basis) 与现金流及单期贷款模型(中英双语)

具体请参考&#xff1a;https://web.stanford.edu/~boyd/vmls/ 下面的例子来源于这本书。 线性代数基础与应用&#xff1a;基底 (Basis) 与现金流及单期贷款模型 在线性代数中&#xff0c;基底&#xff08;Basis&#xff09;是一个重要的概念&#xff0c;广泛应用于信号处理、…

OpenGL ES 03 加载3张图片并做混合处理

OpenGL ES 02 加载3张图片并做混合处理 什么是纹理单元纹理单元的作用使用纹理单元的步骤详细解释加载图片并绑定到到GPU纹理单元采样器的设置1.设置采样器变量的纹理单元编号&#xff0c;目的是为了告诉纹理采样器&#xff0c;从哪个纹理单元采集数据2.如果你没有显式地设置采…

Ubuntu22.04安装CH340/CH341驱动

陈拓 2024/12/20-2024/12/20 1. 我的系统 硬件系统架构 arch 操作系统版本 lsb_release -a 2. CH340G&#xff0c;USB-串口转换器 3. Ubuntu22.04安装CH340驱动 3.1 用lsusb查看USB 插上CH340之前 插上CH340之后 输出中包含ID 1a86:7523 QinHeng Electronics CH340 serial…