JavaEE初阶-多线程易忘点总结

news2024/11/24 17:16:00

文章目录

  • 1.PCB
      • PID
      • 文件描述符表
      • 内存指针
      • 状态
      • 上下文
      • 优先级
      • 记账信息
      • tgid
  • 2.线程与进程的区别
  • 3.sleep和interrupt方法的关系
      • 变量终止线程
      • interrupt方法终止线程
  • 4.线程状态
  • 5.出现线程不安全的原因
      • 线程在系统中是随即调度,抢占式执行的。
      • 多个线程修改同一个变量
      • 线程针对变量的修改操作不是“原子”的
      • 内存可见性
      • 指令重排序
  • 6.死锁发生的三种场景
      • 锁是不可重入锁,一个线程针对同一个锁对象连续加锁多次。
      • 两个线程两把锁。
      • N个线程,M把锁。
  • 7.死锁的必要条件(背)
      • 锁具有互斥性特性(基本特点)
      • 锁不可抢占(不可剥夺)(基本特点)
      • 请求和保持(代码结构)
      • 循环等待(代码结构)
  • 8.单例模式中的饿汉模式与懒汉模式的区别
      • 饿汉模式
      • 懒汉模式
  • 9.编译器优化
      • 内存可见性
      • 指令重排序
  • 10.阻塞队列-生产者消费者模型
      • 解耦合
      • 削峰填谷
  • 11.线程池
  • 12.定时器


1.PCB

PID

不同线程的PID是不同的。

文件描述符表

记录使用的文件资源。

内存指针

指向线程要使用数据以及指令。

状态

指明系统状态。

上下文

当线程切换出cpu停止执行,此时上下文会记录中间结果,方便切换回cpu后继续执行,这个过程和程序计数器有关。

优先级

给线程分配在cpu上执行的时间存在倾斜。

记账信息

操作系统也要避免一些线程一直吃不到资源,记录时间,给吃的少的多分配一点资源。

tgid

是进程的id,同一个进程下的不同线程是相同的。

2.线程与进程的区别

参考以下博客

3.sleep和interrupt方法的关系

变量终止线程

package Thread;

public class Demo10 {
    private static boolean isRunning=true;
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            while (isRunning) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程已经终止");
        });

        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("准备终止线程");
        isRunning=false;

    }
}

通过在主线程中修改变量的值,来跳出t线程中的循环。但是有一个缺点就是即使修改了变量循环可能也不会立刻结束,因为修改变量时可能线程t代码刚好执行到sleep,所以t不会立马终止,至少要等这一次循环执行完成后才能够终止。

interrupt方法终止线程

package Thread;

public class Demo11 {

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted())
                System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        });

        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        t.interrupt();
    }
}

使用interrupt方法可以修改Thread.currentThread().isInterrupted()这个函数值为true,从而终止上述代码的线程t。如果说使用interrupt方法时线程t代码刚好执行到循环条件,那么t直接终止,如果使用interrupt方法时线程又执行到sleep,interrupt方法会直接唤醒线程t,但是同时会将Thread.currentThread().isInterrupted()这个函数值重新变为false,为了避免循环继续进行,此时就可以在sleep被唤醒的哪个trycatch中加入处理逻辑。

4.线程状态

在这里插入图片描述

5.出现线程不安全的原因

线程在系统中是随即调度,抢占式执行的。

多个线程修改同一个变量

线程针对变量的修改操作不是“原子”的

内存可见性

指令重排序

6.死锁发生的三种场景

锁是不可重入锁,一个线程针对同一个锁对象连续加锁多次。

两个线程两把锁。

N个线程,M把锁。

7.死锁的必要条件(背)

锁具有互斥性特性(基本特点)

一个线程拿到锁,如果另一个线程想要申请同一个锁就要阻塞等待。

锁不可抢占(不可剥夺)(基本特点)

一个线程拿到锁,除非自己释放,否则别人拿不走。

请求和保持(代码结构)

一个线程拿到一把锁之后,在不释放锁的前提下,去尝试获取其它锁。

循环等待(代码结构)

多个线程获取多个锁的过程中,出现了循环等待,A等待B,B又等待A。当代码中确实需要多个线程获取多把锁,约定好加锁的顺序,这样就能避免死锁。

8.单例模式中的饿汉模式与懒汉模式的区别

饿汉模式

package Thread;


class Singleton {
    public static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() {


    }
}

public class Demo31 {

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        System.out.println(s1 == s2);


    }
}

懒汉模式

package Thread;
class Singleton1 {

    public static Object locker = new Object();
    public static Singleton1 instance = null;

    public static Singleton1 getInstance() {

        if (instance == null) { //避免已经建立了对象重新上锁浪费性能,直接返回对象即可
            synchronized (locker) {
                if (instance == null) { //避免在多线程情况下重复创建对象,造成线程安全问题
                    instance = new Singleton1();
                }
            }
        }

        return instance;
    }

    private Singleton1() {

    }
}

public class Demo32 {

    public static void main(String[] args) {
        Singleton1 s1 = Singleton1.getInstance();
        Singleton1 s2 = Singleton1.getInstance();


        System.out.println(s1 == s2);

    }
}

饿汉模式在多线程的情况下使用getInstance方法是安全的,因为类对象已经创建好了,getInstance方法做的只是读。懒汉模式则是不安全的,因为在其getInstance方法中会创建类对象。通过给代码加锁会解决懒汉模式的线程安全问题,但是懒汉模式只有在创建类对象实例的时候会出现线程安全问题,创建以后也就是读。为了避免每次都要给代码加上一个锁给程序增加负担,在sychroinzed前面加上一个if语句进行判断,如果已经创建实例了就不用加锁了。

9.编译器优化

内存可见性

package Thread;

import java.util.Scanner;

public class Demo27 {
    private  static int count=0;
    public static void main(String[] args) {

        Thread t=new Thread(()->{

            while(count==0) {
                //
            }
            System.out.println("t1 执行结束");
        });

        Thread t2=new Thread(()->{
            Scanner scanner=new Scanner(System.in);
            System.out.println("输入数字:");

            count=scanner.nextInt();
        });


        t.start();
        t2.start();

    }

}

下面这段代码会出现内存的可见性问题,将从内存中读取count值的操作称为load 判断操作称为cmp,load和cmp的执行速度差了好几个数量级,在线程2开始执行代码提示输入数字时,线程1的while循环已经执行了很多遍。java编译器会自动给代码进行优化,导致load只是第一次时真正从内存中读取count值,其余都是从cpu的寄存器中读取,然而线程2修改count是在内存中进行修改,线程1根本访问不到count的值,可以在变量前加上volatile关键字来提醒编译器不要优化。

指令重排序

指令重排序指的是编译器优化的一种,改变指令在cpu上执行的顺序,但是不影响最终的逻辑结果。对于单线程这样不会出现问题,但是多线程不行。

package Thread;

//单例模式-懒汉模式
//在多线程的情况下是不安全的
class Singleton1 {

    public static Object locker = new Object();
    public static Singleton1 instance = null;

    public static Singleton1 getInstance() {

        if (instance == null) { //避免已经建立了对象重新上锁浪费性能,直接返回对象即可
            synchronized (locker) {
                if (instance == null) { //避免在多线程情况下重复创建对象,造成线程安全问题
                    instance = new Singleton1();
                }
            }
        }

        return instance;
    }

    private Singleton1() {

    }
}

public class Demo32 {

    public static void main(String[] args) {
        Singleton1 s1 = Singleton1.getInstance();
        Singleton1 s2 = Singleton1.getInstance();


        System.out.println(s1 == s2);

    }
}

举例说这里的懒汉模式的代码,在getInstance方法中建立对象分三步
(1)为对象申请空间。
(2)初始化空间(调用构造函数)。
(3)将地址赋给对象的引用。
现在假设一种情况,t1线程在执行上面的懒汉模式的getInstance方法时,因为编译器优化建立对象的指令顺序变为了1,3,2,那么如果t1线程运行到3时刚好t2线程运行到getInstance方法中的第一个判断语句,发现此时instance引用已经被赋值过了就直接返回,但是实际上这里的instance只是得到了地址,地址指向的空间并未初始化,这种情况就是指令重排序所造成的线程安全问题。
解决这种问题的方式也很简单,在你要处理的变量前面加上volatile即可告诉编译器这里不需要优化。

10.阻塞队列-生产者消费者模型

生产者消费者模型的两个优势:
(1)削峰填谷
(2)解耦合
一般在一个进程内的多线程中使用阻塞队列实现生产者消费者模型,在分布式系统中使用消息队列来实现。消息队列就是根据topic分为不同的阻塞队列,根据topic对不同的阻塞队列上进行操作。

解耦合

在这里插入图片描述
如果直接让服务器A和服务器B进行交互,那么它们必定会包含很多与彼此相关的代码。修改A会影响到B,修改B也会影响到A。
在这里插入图片描述
如上图引入一个消息队列,这样A只关心与队列的交互,B也只关心与队列的交互,因此A和B之间的互相影响就被减小非常多。

削峰填谷

在这里插入图片描述
对于服务器A客户端可能会突然发来大量请求,A的处理比较简单,A将请求发送给B,B接收处理的开销相对较大,一旦请求数目过多,B就会挂掉。使用一个条件队列来接收A发送给B的请求,这样无论A发送的请求数目有多少,B都可以按照自己的节奏来处理请求。

11.线程池

我们引入线程就是因为进程创建销毁的代价比较大,但是随着发展,客户端向服务器发送的请求可能呈指数增长,使用线程也觉得创建销毁的开销大了,所以引入线程池以及协程的概念,协程暂不讨论。
为什么引入线程池能够提高效率?因为创建销毁线程的操作主要是用户态以及内核态代码配合完成的工作,但是线程池先提前将线程创建好,然后建立好数据结构保存这些线程,需要线程直接拿,不需要了就放回去,这样的过程全是用户态的就节省了开销,避免与内核态交互。
另外线程池的使用主要分为Executors.newFixedThreadPool这种包装的线程池以及ThreadPoolExecutor这种标准的线程池的类,前者简单参数少,后者参数多更精细。

12.定时器

基本使用就是Timer类,然后构造对象就是需要两个参数,第一个是要执行的任务,第二个就是时间。构建这样的对象之后,定时器中的线程就会自动在你指定的第二个参数时间后去执行你指定的任务。

package Thread;

import java.util.Timer;
import java.util.TimerTask;

public class Demo39 {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(3333);
            }
        }, 3333);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(2222);
            }
        }, 2222);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(1111);
            }
        }, 1111);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1640612.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小白也能微调大模型:LLaMA-Factory使用心得

大模型火了之后,相信不少人都在尝试将预训练大模型应用到自己的场景上,希望得到一个垂类专家,而不是通用大模型。 目前的思路,一是RAG(retrieval augmented generation),在模型的输入prompt中加入尽可能多的“目标领域…

Linux-管道通信

1. 管道概念 管道,是进程间通信的一种方式,在Linux命令中“ | ”就是一种管道,它可以,连接前一条命令,和后一条命令,把前面命令处理完的内容交给后面,例如 cat filename | grep hello …

富文本编辑器CKEditor4简单使用-07(处理浏览器不支持通过工具栏粘贴问题 和 首行缩进的问题)

富文本编辑器CKEditor4简单使用-07(处理浏览器不支持通过工具栏粘贴问题 和 首行缩进的问题) 1. 前言——CKEditor4快速入门2. 默认情况下的粘贴2.1 先看控制粘贴的3个按钮2.1.1 工具栏粘贴按钮2.1.2 存在的问题 2.2 不解决按钮问题的情况下2.2.1 使用ct…

三维图形学知识分享---求平面与模型相交线

在CGAL(Computational Geometry Algorithms Library)中,Polygon_mesh_processing模块提供了用于处理多边形网格数据结构的功能。其中,surface_intersection函数是用来计算模型的表面相交线的工具。 CGAL_Mesh mesh_orcl;std::vect…

C++ 函数 参数与返回值

#一 参数与返回值 回顾文件读数据功能 文件读数据 1函数参数传值调用过程 将函数调用语句中的实参的一份副本传给函数的型材。 简单的值的传递,实参的值没有发生变化。 2 函数参数传值调用过程 传地址调用 将变量的地址传递给函数的形参 形参和实参指向了同…

SpringBoot文件上传+拦截器

1、resource static下有个图片,希望浏览器可以查看这个图片 访问: 若yml设置路径,则可以定义在static下才可以访问 classpath代表类路径,都在target下 也就是项目在运行后的resource下的文件都会到classes下去 无需在target下创…

MES(制造执行系统)与PDCA循环,斩不断理还乱的关系。

MES系统算是B端系统中比较复杂的一种,这与我国制造业标准化程度较低有一定的关联,MES的存在就是要更好执行PDCA循环,二者关联是千丝万缕的,B系统提升专家借此为大家分享一下。 一、什么是PDCA PDCA(Plan-Do-Check-Ac…

前端Web开发基础知识

HTML定义 超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。 什么是 HTML? HTML 是用来描述网页的一种语言。 HTML 指的是超文本标记语言: HyperText Markup LanguageH…

# IDEA 复制项目 Module 出现 不同模块下的 Product 类报错

IDEA 复制项目 Module 出现 不同模块下的 Product 类报错 我们 用 IDEA 复制项目 Module 出现 不同模块下的 Product 类报错,发现复制的 module 名称没有改变或者 java 文件夹后面还有原项目 source root 字样,maven 父子项目没有标识等问题。 解决方法…

QQ+微信聊天记录分析工具,allin~

QQ群 ... QQ个人 微信群 个人朋友圈 更多维度有待探索~ 工具下载 TencentRecordAnalysisV1.0.2.zip 蓝奏云:链接: lanzoub.com/b00rn0g47e 密码:9hww 百度云:链接: pan.baidu.com/s/1Gf5EpJ 提取码: hp2p

Stm32CubeMX 为 stm32mp135d 添加 adc

Stm32CubeMX 为 stm32mp135d 添加 adc 一、启用设备1. adc 设备添加2. adc 引脚配置2. adc 时钟配置 二、 生成代码1. optee 配置 adc 时钟和安全验证2. linux adc 设备 dts 配置 bringup 可参考: Stm32CubeMX 生成设备树 一、启用设备 1. adc 设备添加 启用adc设…

R语言学习—1—将数据框中某一列数据改成行名

将数据框中某一列数据改成行名 代码 结果

DHCPv4_CLIENT_ALLOCATING_03: 发送DHCPREQUEST - 必须包含‘服务器标识符‘

测试目的: 验证客户端发送的DHCPREQUEST消息中是否包含“服务器标识符”选项,以指示它选择的服务器。 描述: 本测试用例旨在确保DHCP客户端在广播DHCPREQUEST消息时,必须包含“服务器标识符”选项。该选项用于指明客户端选择了…

Universal Thresholdizer:将多种密码学原语门限化

参考文献: [LS90] Lapidot D, Shamir A. Publicly verifiable non-interactive zero-knowledge proofs[C]//Advances in Cryptology-CRYPTO’90: Proceedings 10. Springer Berlin Heidelberg, 1991: 353-365.[Shoup00] Shoup V. Practical threshold signatures[C…

[嵌入式系统-53]:嵌入式系统集成开发环境大全 ( IAR Embedded Workbench(通用)、MDK(ARM)比较 )

目录 一、嵌入式系统集成开发环境分类 二、由MCU芯片厂家提供的集成开发工具 三、由嵌入式操作提供的集成开发工具 四、由第三方工具厂家提供的集成开发工具 五、开发工具的整合 5.1 Keil MDK for ARM 5.2 IAR Embedded Workbench(通用)、MDK&…

240503-关于VisualStudio2022社区版的二三事

240503-关于VisualStudio2022社区版的二三事 1 常用快捷键 快捷键描述AltEnter选中代码片段以提取方法Alt上下箭头移动选中的代码片段F12转到方法定义CtrlR*2批量修改选中的变量名称 2 自动生成构造函数 3 快速重写父类方法 4 节约时间:写代码使用“头插法”&…

深度解析 Spring 源码:从BeanDefinition源码探索Bean的本质

文章目录 一、BeanDefinition 的概述1.1 BeanDefinition 的定位1.2 BeanDefition 的作用 二、BeanDefinition 源码解读2.1 BeanDefinition 接口的主要方法2.2 BeanDefinition 的实现类2.2.1 实现类的区别2.2.2 setBeanClassName()2.2.3 getDependsOn()2.2.4 setScope() 2.3 Bea…

用双目相机实现坐标标定

一:相机参数设置和计算 镜头参数:MF2808-10MP 靶面尺寸2/3 ,视场角(对角水平垂直) 69.758.545.5 焦距:8mm,分辨率:16241240 1.1视场角的计算 图像分辨率越高,双目匹…

FP16、BF16、INT8、INT4精度模型加载所需显存以及硬件适配的分析

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

Arduino 推出带 Wi-Fi的 32 位 UNO 板

Arduino 推出了下一代 UNO 板,引入了 32 位 Renesas 微控制器和 Espressif ESP32-S3 模块、一键云连接和大量 I/O 以及 128 红色 LED 矩阵。新型 UNO R4 板有两个版本,带 Wi-Fi 连接和不带 Wi-Fi 连接,并保持了 UNO R3 的外形尺寸、屏蔽兼容性…