并发问题系统学习(更新中)

进程、线程

  • 进程:进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。可以理解为一个java应用。

  • 线程:线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

一个进程中有多个线程,多个线程共用进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈。但会导致内存泄漏上下⽂切换死锁。

多线程和锁的关系

只有拿到锁的线程才能访问共享资源,多线程之间的通信和协作,通常使用锁和等待/通知机制来实现。

线程死锁

线程 A 持有资源 2 ,线程 B 持有资源 1 ,他们同时都想申请对⽅的资源,所以这两个线程就会互相等待⽽进⼊死锁状态。当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack

并发、并行

简单说,轮流做是并发,一起做是并行。

线程创建方式

  • 继承Thread类,重写run()方法,调用start()方法启动线程

一个例子

这段代码输出结果可能是ab或者ba。

  • 实现 Runnable 接口,重写run()方法

上面两种都是没有返回值的。

  • 实现Callable接口,重写call()方法,这种方式可以通过FutureTask获取任务执行的返回值

JVM执行start方法,会先创建一条线程,由创建出来的新线程去执行thread的run方法,这才起到多线程的效果。直接执行run方法就相当于执行一个普通的方法。直接执行thread中的run方法也是相当于顺序执行run方法。

线程等待、休眠与通知

等待

休眠

等待和休眠区别

通知

实例

执行顺序

停止线程

  1. 使用退出标志,使线程正常退出。
    volatile boolean flag = false ;t1.flag = true ;
  2. stop强行终止t1.stop();
  3. interrupt
    Thread t2 = new Thread(()->{
    while(true) {
    Thread current = Thread.currentThread();
    boolean interrupted = current.isInterrupted();
    if(interrupted) {
    System.out.println("打断状态:"+interrupted);
    break;
    }
    }
    }, "t2");
    t2.start();
    Thread.sleep(500);

线程上下文切换

使用多线程的目的是为了充分利用CPU,但是我们知道,并发其实是一个CPU来应付多个线程。

为了让用户感觉多个线程是在同时执行的, CPU 资源的分配采用了时间片轮转也就是给每个线程分配一个时间片,线程在时间片内占用 CPU 执行任务。当线程使用完时间片后,就会处于就绪状态并让出 CPU 让其他线程占用,这就是上下文切换。

守护线程

Java中的线程分为两类,分别为 daemon 线程(守护线程)和 user 线程(用户线程)。

在JVM 启动时会调用 main 函数,main函数所在的线程就是一个用户线程。其实在 JVM 内部同时还启动了很多守护线程, 比如垃圾回收线程。

那么守护线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程束时, JVM会正常退出,而不管当前是否存在守护线程,也就是说守护线程是否结束并不影响 JVM退出。换而言之,只要有一个用户线程还没结束,正常情况下JVM就不会退出。

线程间有哪些通信方式(操作系统)

volatile 的作用主要

  • 保证可见性: 当一个变量被声明为 volatile 后,对该变量的写操作会立即被其他线程所看到,保证了多个线程之间对该变量的可见性。换句话说,一个线程对 volatile 变量的修改对其他线程是可见的,不会出现线程间的数据不一致问题。例:

static volatile boolean stop = false;

  • 禁止指令重排序: volatile 变量的读写操作会插入内存屏障,防止编译器和处理器对其进行指令重排序优化,保证了代码执行的顺序性。这样可以确保对 volatile 变量的写操作先于后续的读操作,避免出现因指令重排序导致的意外结果。

写变量让volatile修饰的变量的在代码最后位置
读变量让volatile修饰的变量的在代码最开始位置

总的来说,volatile 主要用于在多线程环境中确保变量的可见性和一致性,它适用于一种场景:变量被多个线程共享,并且这些线程可能会同时读写这个变量。通过使用 volatile 关键字,可以有效地避免由于线程间数据不一致导致的并发问题。

并发锁

synchronized【对象锁】关键字的使用

synchronized 关键字解决的是多个线程之间访问资源的同步性, synchronized 关键字可以保证
被它修饰的⽅法或者代码块在任意时刻只能有⼀个线程执⾏。Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。
1. 修饰代码块 synchronized (this) 和 synchronized (lock)
指定加锁对象,对给定对象 / 类加锁。 synchronized(this/object) 表示进⼊同步代码库前要获得给定对象的锁 synchronized(类 .class) 表示进⼊同步代码前要获得 当前 class 的锁。
  1. synchronized (this):这种方式是在非静态方法中使用的,它将当前对象(即调用该方法的对象)作为同步锁。只有在同一对象实例上获取锁的线程才能进入同步代码块,其他线程需要等待当前对象锁释放后才能继续执行。因此,同一对象的不同方法之间也是同步的。

  2. synchronized (lock):这种方式是在静态方法或者普通代码块中使用的,它将指定的对象作为同步锁。通常情况下,会使用一个静态对象作为锁。多个线程在获取到相同的锁对象时才能进入同步代码块,其他线程需要等待锁释放后才能执行。因此,不同对象实例上的同步代码块之间是独立的,不会相互影响。

2. 修饰实例⽅法 : 作⽤于当前对象实例加锁,进⼊同步代码前要获得 当前对象实例的锁
3. 修饰静态⽅法 : 也就是给当前类加锁,会作⽤于类的所有对象实例 ,进⼊同步代码前要获得
class 的锁 。因为静态成员不属于任何⼀个实例对象,是类成员( static 表明这是该类的⼀个
静态资源,不管 new 了多少个对象,只有⼀份 )。所以,如果⼀个线程 A 调⽤⼀个实例对象的
⾮静态 synchronized ⽅法,⽽线程 B 需要调⽤这个实例对象所属类的静态 synchronized ⽅法,
是允许的,不会发⽣互斥现象, 因为访问静态 synchronized ⽅法占⽤的锁是当前类的锁,⽽访
问⾮静态 synchronized ⽅法占⽤的锁是当前实例对象锁
总结:
  • synchronized 关键字加到 static 静态⽅法和 synchronized(class) 代码块上都是是给 Class
类上锁。
  • synchronized 关键字加到实例⽅法上是给对象实例上锁。
  • 尽量不要使⽤ synchronized(String a) 因为 JVM 中,字符串常量池具有缓存功能!

对象单例模式

1.懒汉模式

2.饿汉模式

synchronized底层实现原理

  • synchronized 关键字底层原理属于 JVM 层⾯。
synchronized 同步语句块的实现使⽤的是 monitorenter monitorexit 指令,其中
monitorenter 指令指向同步代码块的开始位置, monitorexit 指令则指明同步代码块的结束位
置。
  • 当执⾏ monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。
  • monitor内部有三个属性,分别是owner、entrylist、waitset。其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程
  • 在执⾏ monitorenter 时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1
  • 在执⾏ monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外⼀个线程释放为⽌。

synchronized和Lock区别

锁升级

Monitor实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。

对象的内存结构

对象在内存中存储的布局:

MarkWord:

  • Monitor重量级锁:每个对象的对象头都可以设置monoitor的指针,让对象与monitor产生关联。
  • 轻量级锁:

  • 偏向锁

锁升级

CPU ⾼速缓存

CPU 缓存则是为了解决 CPU 处理速度和内存处理速度不对等的问题。 CPU Cache 缓存的是内存数据⽤于解决 CPU 处理速度和内存不匹配的问题,内存缓存的 是硬盘数据⽤于解决硬盘访问速度过慢的问题。
先复制⼀份数据到 CPU Cache 中,当 CPU 需要⽤到的时候就可以直接从 CPU Cache 中读取数
据,当运算完成后,再将运算得到的数据写回 Main Memory 中。CPU 为了解决内存缓存不⼀致性问题可以通过制定缓存⼀致协议或者其他⼿段来解决。

JMM(Java 内存模型)(JVM)

Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

CAS

CAS的全称是: Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性。

一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当旧的预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。如果CAS操作失败,通过自旋的方式等待并再次尝试,直到成功。

旧的预期值就是希望从哪个值开始变的,需要跟内存值做对比,如果一样的话就更新内存值为B的数值。

一般思路是通过自旋锁实现。

并发程序出现问题的根本原因

Java并发编程三大特性

原子性:加锁

synchronized或LOCK

可见性:让一个线程对共享变量的修改对另一个线程可见

synchronized、volatile(推荐)、LOCK

有序性

指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保
证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终
执行结果和代码顺序执行的结果是一致的。volatile

AQS

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架,它是构建锁或者其他同步组件的基础框架

AQS常见的实现类:

  • ReentrantLock 阻塞式锁
  • Semaphore 信号量
  • CountDownLatch 倒计时锁

在去修改state状态的时候,使用的cas自旋锁来保证原子性,确保只能有一个线程修改成功,修改失败的线程将会进入FIFO队列中等待.

AQS是公平锁吗,还是非公平锁?

  • 新的线程与队列中的线程共同来抢资源,是非公平锁
  • 新的线程到队列中等待,只让队列中的head线程获取锁,是公平锁
  • 比较典型的AQS实现类ReentrantLock,它默认就是非公平锁,新的线程与队列中的线程共同来抢资源

ReentrantLock

翻译过来是可重入锁,CAS+AQS队列实现。

可中断;可以设置超时时间;可以设置公平锁;支持多个条件变量;与synchronized一样,都支持重入.

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024.05.10作业

TCP服务器 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QList> #include <QMessageBox> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; …

Paddle 基于ANN(全连接神经网络)的GAN(生成对抗网络)实现

什么是GAN GAN是生成对抗网络&#xff0c;将会根据一个随机向量&#xff0c;实现数据的生成&#xff08;如生成手写数字、生成文本等&#xff09;。 GAN的训练过程中&#xff0c;需要有一个生成器G和一个鉴别器D. 生成器用于生成数据&#xff0c;鉴定器用于鉴定数据的准确性&…

国产开源物联网操作系统

软件介绍 RT-Thread是一个开源、中立、社区化发展的物联网操作系统&#xff0c;采用C语言编写&#xff0c;具有易移植的特性。该项目提供完整版和Nano版以满足不同设备的资源需求。 功能特点 1.内核层 RT-Thread内核包括多线程调度、信号量、邮箱、消息队列、内存管理、定时器…

[c++]多态的分析

多态详细解读 多态的概念多态的构成条件 接口继承和实现继承: 多态的原理:动态绑定和静态绑定 多继承中的虚函数表 多态的概念 -通俗的来说&#xff1a;当不同的对象去完成某同一行为时&#xff0c;会产生不同的状态。 多态的构成条件 必须通过基类的指针或者引用调用虚函数1虚…

合并两个有序链表(C语言)———链表经典算法题

题目描述​​​​​​21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09;&#xff1a; 答案展示: 迭代&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* mergeTwoLis…

【docker 】push 镜像到私服

查看镜像 docker images把这个hello-world 推送到私服 docker push hello-world:latest 报错了。不能推送。需要标记镜像 标记Docker镜像 docker tag hello-world:latest 192.168.2.1:5000/hello-world:latest 将Docker镜像推送到私服 docker push 192.168.2.1:5000/hello…

【吃透Java手写】2-Spring(下)-AOP-事务及传播原理

【吃透Java手写】Spring&#xff08;下&#xff09;AOP-事务及传播原理 6 AOP模拟实现6.1 AOP工作流程6.2 定义dao接口与实现类6.3 初始化后逻辑6.4 原生Spring的方法6.4.1 实现类6.4.2 定义通知类&#xff0c;定义切入点表达式、配置切面6.4.3 在配置类中进行Spring注解包扫描…

python中如何把list变成字符串

python中如何把list变成字符串&#xff1f;方法如下&#xff1a; python中list可以直接转字符串&#xff0c;例如&#xff1a; data ["hello", "world"] print(data1:,str(data)) 得到结果&#xff1a; (data1:, "[hello, world]") 这里将整个…

芋道----工作流中添加邮件通知

1、配置邮件发送的账号 2、编辑邮件的内容模板 如何新建邮箱&#xff0c;直接查看芋道官网即可&#xff0c;已经讲解的很详细了&#xff0c;可以直接点击下方链接 邮件配置 | ruoyi-vue-pro 开发指南 (iocoder.cn)https://doc.iocoder.cn/mail/#_3-1-%E6%96%B0%E5%BB%BA%E9%82…

Android项目转为鸿蒙,真就这么简单?

最近做了一个有关Android转换成鸿蒙的项目。经不少开发者的反馈&#xff1b;许多公司的业务都增加了鸿蒙板块。 对此想分享一下这个项目转换的流程结构&#xff0c;希望能够给大家在工作中带来一些帮助。转换流程示意图如下&#xff1a; 下面我就给大家介绍&#xff0c;Android…

26、Qt使用QFontDatabase类加载ttf文件更改图标颜色

一、图标下载 iconfont-阿里巴巴矢量图标库 点击上面的链接&#xff0c;在打开的网页中搜索自己要使用的图标&#xff0c;如&#xff1a;最大化 找到一个自己想用图标&#xff0c;选择“添加入库” 点击“购物车”图标 能看到刚才添加的图标&#xff0c;点击“下载代码”(需要…

手撕C语言题典——移除链表元素(单链表)

目录 前言 一.思路 1&#xff09;遍历原链表&#xff0c;找到值为 val 的节点并释放 2&#xff09;创建新链表 二.代码实现 1)大胆去try一下思路 2&#xff09;竟然报错了&#xff1f;&#xff01; 3&#xff09;完善之后的成品代码 搭配食用更佳哦~~ 数据结构之单…