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

day23-线程篇(一)

目录

一.多线程基础

概述:

1.进程与线程

1.1什么是程序?

1.2 什么是进程?

1.3什么是线程?

1.4 进程与线程的区别

2. 线程基本概念

 3. 线程的创建与启动

4. 线程的创建方式

5. 线程的命名

6. 线程的休眠(暂停)

 7.线程的优先级

8.小结

二. 线程的状态及常用方法

1.线程的状态

2. 线程的插队:join( )方法

 3. join( )方法和sleep( )方法的区别

4. 线程的中断:interrupt( )方法

5. 线程的让出:yield( )方法

5.1 yield( )方法的作用

6. 守护线程(Daemon Thread)

6.1 用户线程与守护线程的区别

6.2 设置守护线程

三.Synchronized同步锁

1.什么是Synchronized同步锁?

2.synchronized 关键字的用法

3.synchronized修饰实例方法

4.synchronized修饰静态方法

5.synchronized修饰代码块

6.synchronized 关键字的补充


一.多线程基础

概述:

                现代操作系统(WindowsmacOSLinux)都可以执行多任务。多任务就是同时运行多个任务。例如:播放音乐的同时,浏览器可以进行文件下载,同时可以进行QQ消息的收发。

1.进程与线程

1.1什么是程序?

        程序是含有指令和数据的文件,被存储在磁盘或其他数据存储设备中,可以理解为程序是包含静态代码的文件,例如:浏览器软件,音乐器播放器。

1.2 什么是进程?

        进程是程序的一次执行过程,是系统运行程序的基本单位。在windows系统中,每一个正在执行的exe文件或后台服务,都是一个进程,由操作系统统一管理并分配资源,因此进程是动态的。

例如:正在运行的浏览器就是一个进程。

1.3什么是线程?

        某些进程内部还需要同时执行多个子任务。例如:我们在使用WPS时,WPS可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行自动保存和上传云文档,我们把子任务称为线程

        进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个主线程。

                        ┌──────────┐│Process   ││┌────────┐│┌──────────┐││ Thread ││┌──────────┐│Process   ││└────────┘││Process   ││┌────────┐││┌────────┐││┌────────┐│
┌──────────┐││ Thread ││││ Thread ││││ Thread ││
│Process   ││└────────┘││└────────┘││└────────┘│
│┌────────┐││┌────────┐││┌────────┐││┌────────┐│
││ Thread ││││ Thread ││││ Thread ││││ Thread ││
│└────────┘││└────────┘││└────────┘││└────────┘│
└──────────┘└──────────┘└──────────┘└──────────┘
┌──────────────────────────────────────────────┐
│               Operating System               │
└──────────────────────────────────────────────┘

         线程是一个比进程更小的执行单位(CPU的最小执行单位)。一个进程在其执行的过程中可以产生多个线程。与进程不同的是,同类的多个线程共享同一块内存空间和一组系统资源, 所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多。

1.4 进程与线程的区别
  • 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。
  • 资源开销:每个进程都有独立的代码副本和数据空间,进程之间的切换,资源开销较大;线程可以看作是轻量级的进程,每个线程都有自己独立的运行栈和程序计数器,线程之间的切换,资源开销小。
  • 包含关系:一个进程内包含有多个线程,在执行过程中,线程的执行不是线性串行的,而是多条线程并行共同完成;
  • 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响;一个线程崩溃,会导致整个进程退出。所以多进程要比多线程健壮;
  • 执行过程:每个独立的进程有程序运行的入口和程序出口。但是线程不能独立执行,必须依存在应用程序(进程)中,由应用程序提供多个线程执行控制;
// 程序:程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,可以理解为程序是包含静态代码的文件
// 多进程:进程是程序的一次执行过程,是系统运行程序的基本单位。
// 多线程:线程是操作系统运行时,能够调度的最小单位,它被包含在进程中,时进程在运行过程中的一个单位
// 多线程的应用场景:
// 软件中的耗时操作,拷贝和迁移文件,加载大量的资源的时候
// 所有的后台服务器
// 所有的聊天软件

2. 线程基本概念

单线程:单线程就是进程中只有一个线程。

public class SingleThread {public static void main(String[] args) {for (int i = 0; i < 10000; i++) {System.out.print(i + " ");}}
}

 多线程:由一个以上的线程组成的程序称为多线程程序。Java中,一定是从主线程开始执行(main方法),然后在主线程的某个位置创建并启动新的线程。

public class MultiThread {public static void main(String[] args) {// 创建2个线程Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("线程1:" + i + " ");}}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("线程2:" + i + " ");}}});// 启动2个线程t1.start();t2.start();}
}

 3. 线程的创建与启动

  • 通过创建Thread实例,完成线程的创建。
    • 线程的内部实现可以通过继承Thread类、实现Runnable接口等方式进行封装。
  • 通过调用Thread实例的start()方法启动新线程。
  • 查看Thread类的源代码,会看到start()方法内部调用了一个private native void start0()方法,native修饰符表示这个方法是由JVM虚拟机内部的C代码实现的本地方法,由JVM根据当前操作系统进行本地实现。
package thread;public class Demo01 {public static void main(String[] args) {for (int i = 0; i < 100; i++){System.out.println("main线程:" + i);}ThreadOne t1 = new ThreadOne();t1.start();
//        for (int i = 0; i < 100; i++){
//            System.out.println("main线程:" + i);
//        }}
}

 注意:直接调用Thread实例的run()方法是无效的,因为直接调用run()方法,相当于调用了一个普通的Java方法,当前线程并没有任何改变,也不会启动新线程。

4. 线程的创建方式

方式1:

通过继承Thread,重写Thread类中run()方法。main主线程中new一个Thread的子类,然后调用start()方法。

package thread;public class ThreadOne extends  Thread{public ThreadOne(String name){super(name);}public ThreadOne(){super();}public void run(){for(int i = 0; i < 100; i++){System.out.println(getName() + ":" + i);}}
}mainThreadOne t1 = new ThreadOne();t1.start();

方式2:java.lang.Runnable 接口实现多线程,创建一个实现Runnable接口的实现类,重写run方法

创建Runable的实现类的对象r1,创建Thread类对象,构造方法中传递Runnable接口的实现类对象

调用start方法的启动线程。

 public static void main(String[] args) {ThreadTwo t2 = new ThreadTwo();Thread t = new Thread(t2);t.start();for (int i=0; i < 100; i++){System.out.println("main线程:" + i);}}
}
class ThreadTwo implements Runnable{public void run(){for(int i = 0; i < 100; i++){System.out.println("线程1:" + i);}}
}

方式3:实现 java.util.concurrent.Callable 接口,允许子线程返回结果、抛出异常。

// 实现子线程
public class SubThread implements Callable<Integer>{private int begin,end;public SubThread(int begin,int end){this.begin = begin;this.end = end;}@Overridepublic Integer call() throws Exception {int result = 0;for(int i=begin;i<=end;i++){result+=i;}return result;}
}

5. 线程的命名

  • 调用父类的setName()方法或在构造方法中给线程名字赋值。
  • 如果没有为线程命名,系统会默认指定线程名,命名规则是Thread-N的形式。
package thread.method;import thread.ThreadOne;public class Demo01 {// 给线程设置名字// setName()方式设置线程名// 调用有参构造设置线程名public static void main(String[] args) {ThreadOne t1 = new ThreadOne();t1.setName("线程1");t1.start();// 使用构造方法设置线程名ThreadOne t2 = new ThreadOne("线程2");t2.start();// Runable作为参数Runnable r = new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"线程正在运行");}};Thread t3 = new Thread(r,"西红柿");t3.start();}
}

6. 线程的休眠(暂停)

可以调用Thread.sleep(long millis),强迫当前相乘按照毫秒值休眠。

package thread.method;import java.sql.SQLOutput;public class Demo08 {public static void main(String[] args) throws InterruptedException {System.out.println("main线程进入");Thread t1 = new Thread(new Runnable() {@Overridepublic void  run() {// 获取当前的系统时间long start = System.currentTimeMillis();System.out.println("进入t1线程");try {Thread.sleep(5000);} catch (InterruptedException e) {System.out.println("中断t1线程,消耗时间为:"+(System.currentTimeMillis()-start)+"毫秒");e.printStackTrace();return;}System.out.println("结束t1线程,消耗时间为:"+(System.currentTimeMillis()-start)+"毫秒");}},"t1线程");t1.start();// 让主线程休眠Thread.sleep(1000*3);System.out.println("main线程结束");// main主线程修改t1线程中断状态=true// t1线程检测中断状态=true,则抛出InterruptedException异常, 子线程执行结束
//        t1.interrupt();}
}

 7.线程的优先级

  • 在线程中通过setPriorty(int n)设置线程优先级,范围为1-10,默认为5.
  • 优先级高的线程被操作系统调度的优先级较高。

提示:并不能代表,通过设置优先级来确保高优先级的线程一定会先执行。

8.小结

  • Java用Thread对象表示一个线程,通过调用start()启动一个新线程;
  • 一个线程对象只能调用一次start()方法;
  • 线程的执行代码写在run()方法中;
  • 线程调度由操作系统决定,程序本身无法决定调度顺序;

二. 线程的状态及常用方法

1.线程的状态

        在Java程序中,一个线程对象通过调用start()方法启动线程,并且在线程获取CPU时,自动执行run()方法。run()方法执行完毕,代表线程的生命周期结束。

 

  • 线程终止的原因有:
    • 线程正常终止:run()方法执行到return语句返回;
    • 线程意外终止:run()方法因为未捕获的异常导致线程终止;
    • 对某个线程的Thread实例调用stop()方法强制终止(宇宙超级无敌强烈不推荐);

2. 线程的插队:join( )方法

public class Main {public static void main(String[] args) throws InterruptedException {System.out.println("主线程Main:开始执行,即将创建并调用子线程");// 创建并启动子线程MyThread myThread = new MyThread();myThread.start();// 主线程调用myThread子线程的join()方法myThread.join(); // 子线程插队,插入到当前线程main的执行序列前System.out.println("主线程Main:当子线程myThread执行完毕后,主线程Main再执行");}
}
    • 综上所述:join()方法实际上是通过调用wait()方法, 来实现同步的效果的。
      • 例如:A线程中调用了B线程join()方法,则相当于A线程调用了B线程wait()方法,在调用了B线程wait()方法后,A线程就会进入WAITING或者TIMED_WAITING等待状态,因为它相当于放弃了CPU的使用权。
    • 注意:join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕:即join(0)=join()
public class Thread implements Runnablepublic final void join() throws InterruptedException {join(0);}public final synchronized void join(long millis) throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {// 无限等待wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}// 计时等待wait(delay);now = System.currentTimeMillis() - base;}}}
}

 3. join( )方法和sleep( )方法的区别

    • 两个方法都可以实现类似"线程等待"的效果,但是仍然有区别;
    • join()是通过在内部使用synchronized + wait()方法来实现的,所以join()方法调用结束后,会释放锁;
    • sleep()休眠没有结束前,不会释放锁;

4. 线程的中断:interrupt( )方法

        如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。

        注意事项:

  • 线程被Object.wait(), Thread.join()Thread.sleep()三种方法阻塞或等待,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常,从而提前终结被阻塞状态。
  • 如果线程没有被阻塞或等待,调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()等方法进入阻塞或等待时,才会抛出 InterruptedException异常;

5. 线程的让出:yield( )方法

5.1 yield( )方法的作用
    • 线程通过调用yield()方法告诉JVM的线程调度,当前线程愿意让出CPU给其他线程使用。
    • 至于系统是否采纳,取决于JVM的线程调度模型:分时调度模型抢占式调度模型
      • 分时调度模型:所有的线程轮流获得 cpu的使用权,并且平均分配每个线程占用的 CPU 时间片;
      • 抢占式调度模型:优先让可运行池中优先级高的线程占用 CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。(JVM虚拟机采用的是抢占式调度模型 )

6. 守护线程(Daemon Thread)

6.1 用户线程与守护线程的区别
    • 用户线程:我们平常创建的普通线程;
    • 守护线程:用来服务于用户线程的线程,在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出;而守护线程执行结束后,虚拟机不会自动退出。
6.2 设置守护线程
  • 在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程
Thread myThread = new Thread();
myThread.setDaemon(true);
myThread.start();
public class Main {public static void main(String[] args) {long startTime = System.currentTimeMillis();// 创建并启动子线程new Thread() {@Overridepublic void run() {//子线程休眠10秒钟try {Thread.sleep(10*1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("普通用户线程,运行耗时" + (System.currentTimeMillis() - startTime));}}.start();//主线程休眠3秒,确保在子线程之前结束休眠try {Thread.sleep(3*1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Main主线程,运行耗时 " + (System.currentTimeMillis() - startTime));}
}

 运行结果分析:普通用户线程,在没有完成打印内容的时候,JVM是不会被结束。

三.Synchronized同步锁

1.什么是Synchronized同步锁?

   Synchronized同步锁,简单来说,使用Synchronized关键字将一段代码逻辑,用一把锁给锁起来,只有获得了这把锁的线程才访问。并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码,从而确保代码的线程安全.

2.synchronized 关键字的用法

  1. 修饰实例方法:synchronized修饰实例方法, 则用到的锁,默认为this当前方法调用对象;
  2. 修饰静态方法:synchronized修饰静态方法, 则其所用的锁,默认为Class对象;
  3. 修饰代码块:synchronized修饰代码块, 则其所用的锁,是某个指定Java对象;

3.synchronized修饰实例方法

  • 使用当前对象this充当锁完成对当前方法的锁定,只有获取this锁的线程才能访问当前方法;
  • 并发过程中,同一时刻,可以有N个线程请求执行方法,但只有一个线程可以持有this锁,才能执行;
  • 不同线程,持有的对象,必须相同;

当使用synchronized 修饰实例方法时, 以下两种写法作用和意义相同:

public class Foo {// 实例方法public synchronized void doSth1() {// 获取this锁,才能执行该方法}// 实例方法public void doSth2() {synchronized(this) {// 获取this锁,才能执行该代码块}}
}public static void main(String[] args) {// 实例化一个对象Foo fa = new Foo();// 创建不同的线程1Thread thread01 = new Thread() {public void run() {// 使用相同的对象访问synchronized方法fa.doSth1();}};// 创建不同的线程2Thread thread02 = new Thread() {public void run() {// 使用相同的对象访问synchronized方法fa.doSth1();}};// 启动线程thread01.start();thread02.start();
}

4.synchronized修饰静态方法

  • 使用当前对象的Class对象充当锁,完成对当前方法的锁定,只有获取Class锁的线程才能访问当前方法;
  • 不同线程,持有的对象,可以不同,但必须相同class类型;
public class Foo {// 静态方法public synchronized static void doSth1() {// 获取当前对象的Class对象锁,才能执行该方法}// 实例方法public static void doSth2() {synchronized(this.getClass()) {// 获取当前对象的Class对象锁,才能执行该代码块}}
}public static void main(String[] args) {// 创建不同的对象(相同类型)Foo fa = new Foo();Foo fb = new Foo();// 创建不同线程1Thread thread01 = new Thread() {public void run() {// 使用不同的对象访问synchronized方法fa.doSth2();}};// 创建不同线程2Thread thread02 = new Thread() {public void run() {// 使用不同的对象访问synchronized方法fb.doSth2();}};// 启动线程thread01.start();thread02.start();
}

5.synchronized修饰代码块

synchronized(自定义对象) {//临界区
}

6.synchronized 关键字的补充

  • 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

, 所有的线程都可以自由地访问对象中的代码, 而synchronized关键字只是限制了线程对于已经加锁的同步代码块的访问,并不会对其他代码做限制。所以,同步代码块应该越短小越好。

  • 父类中synchronized修饰的方法,如果子类没有重写,则该方法仍然是线程安全性;如果子类重写,并且没有使用synchronized修饰,则该方法不是线程安全的;
  • 在定义接口方法时,不能使用synchronized关键字;
  • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步;
  • 离开synchronized代码块后,该线程所持有的锁,会自动释放;
http://www.lryc.cn/news/610025.html

相关文章:

  • 什么是内容管理系统?
  • 基于实时音视频技术的远程控制传输SDK的功能设计
  • mysql中使用LIMIT分页查询数据出现深分页的原因
  • 【音视频】WebRTC 一对一通话-实现概述
  • SpringMVC在前后端分离架构中的执行流程详解
  • AI绘画-Stable Diffusion-WebUI的ControlNet用法
  • STM32F103C8T6 BC20模块NBIOT GPS北斗模块采集温湿度和经纬度发送到EMQX
  • 攻防世界-easyphp-lever1
  • k8s常见问题
  • 【ECCV2024】AdaCLIP:基于混合可学习提示适配 CLIP 的零样本异常检测
  • Design Compiler:高层次优化与数据通路优化
  • 【Spring Boot 快速入门】六、配置文件
  • Java 发送 HTTP POST请求教程
  • Scikit-learn - 机器学习库初步了解
  • MoonBit Pearls Vol.04:用MoonBit 探索协同式编程
  • Spring IoC容器与Bean管理
  • GPTs——定制的小型智能体
  • 白杨SEO:百度搜索开放平台发布AI计划是什么?MCP网站红利来了?顺带说说其它
  • [Oracle] || 连接运算符
  • 关于如何自定义vscode(wsl连接linux)终端路径文件夹文件名字颜色的步骤:
  • 【PHP】获取图片的主要颜色值RGB值
  • 【Django】-3- 处理HTTP响应
  • Django 性能优化详解:从数据库到缓存,打造高效 Web 应用
  • CNN卷积神经网络之MobileNet和ResNet(五)
  • AWS Lambda Function 全解:无服务器计算
  • CAD格式转换器HOOPS Exchange:全方位支持HOOPS系列产品
  • Webpack 搭建 Vue3 脚手架详细步骤
  • Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现人脸面部表情的追踪识别(C#代码UI界面版)
  • [3D数据存储] Archive (File Container) | 创建/写入/读取 | 存储格式HDF5
  • pyqt5-tools/pyqt6-tools 安装失败,解决办法