并发之美

news/2025/2/16 3:49:25/

在我们日常生活中,事情应该一件一件的做,但是我们会感觉这样不仅效率低下,而且还特别累人,我们何不转换思想,看下图:

我们可以在烧开水的时候,顺便把洗茶具、拿茶叶的事情做了,这样是不是节省了好多的时间,所以并发顾名思义就是好多的线程一起在运行,那线程相关的知识,如果大家忘了,请看(线程详解)!!!

好了,言归正传,那什么是并发编程呢?

并行和并发

并行 :在同一个时间节点上,同时发生(是真正意义上的同时执行)

并发 :在一段时间内,对个事情交替执行

并发编程:在某些特定的场景中,有大量的请求访问同一个资源,会出现线程安全的问题,所以需要通过编程来控制解决让多个线程依次访问资源,称为并发。

并发的根本原因

以前单核CPU时,只能支持单线程模式,现在多核CPU,我们自然而然的可以支持多线程模式,在此之前,我们先浅谈下JMM(java内存模型)

java内存模型:它是java虚拟机规范的一种工作模式,将内存分为主内存和工作内存,变量数据存储在主内存中,线程在操作变量的时候会将主内存中的数据复制一份到工作内存,然后在工作内存中操作完后,再将数据写回主内存中。

多线程的出现,随之而来的也是与之对应的问题,下面我会为大家一一讲解!!!

在比赛的两头,中间隔着巨高的墙体,两位选手都风快的向终点赶去,但是墙体之隔,使他们不知道对方的存在。

可见性:多个线程分别同时对共享数据操作,彼此之间不可见,操作完写回主内存,有可能出现问题

有序性:为了性能,虚拟机对一些代码指令的执行顺序进行重排,以提高速度

/*模拟指令重排序*/
public class Reorder {private   static int x;private   static int y;private   static int a;private   static int b;public static void main(String[] args) throws InterruptedException {int i = 0;for(;;) {i++;x = 0; y = 0;a = 0; b = 0;Thread one = new Thread(new Runnable() {public void run() {a = 1;x = b;}});Thread other = new Thread(new Runnable() {public void run() {b = 1;y = a;}});one.start();other.start();one.join();other.join();String result = "第" + i + "次 (" + x + "," + y + ")";if(x == 0 && y == 0) {System.err.println(result);break;} else {System.out.println(result);}}}
}

在运行的过程中,系统先执行了后面的代码以至于跳出循环,这就是多线程出现的第二个问题无序性

原子性:一个或多个操作在 CPU 执行的过程中不被中断的特性

两次count++,我们应该得到的是count=2,但是结果确实count=1,这就是多线程出现的问题之一(不可见性)

原因

  • 缓存(工作内存)带来了不可见性

  • 指令重排优化带来了无序性

  • 线程切换带来了非原子性

不可见变可见,无序变有序

让我们接下来介绍一个关键字volatile

底层实现原理:在底层指令级别进行控制,volatile修饰的变量在操作前,添加内存屏障,不让它的指令干扰,volatile修饰的变量添加内存屏障之外,还要通过缓存一致性协议(MESI)将数据写回到主内存,其他工作内存嗅探后,把自己的工作内存数据过期,重新从主内存读取最新的数据

不可见变可见:

public class TestVolatile {public static void main(String[] args) {//创建线程任务ThreadDemo td = new ThreadDemo();Thread t = new Thread(td);//创建线程t.start();//main线程中也需要使用flag变量while (true) {if (td.getFlag()) {//false-trueSystem.out.println("main---------------");break;}}}
}
public class ThreadDemo implements  Runnable{/*volatile 修饰的变量,在一个线程中被修改后,对其它线程立即可见禁止cpu对指令重排序*/private (volatile)选加 boolean  flag = false;//共享数据public void run() {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}this.flag = true;//让一个线程修改共享变量值System.out.println(this.flag);}public boolean getFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}
}

没加volatile之前

当共享变量修改后,main方法并不知道变量已经修改,一致在重复着死循环

加volatile后

当共享变量修改后,main方法立刻知道变量已经修改,跳出死循环

无序变有序😛见有序性的代码加以理解

原子性

  1. 加锁

  • synchronized:关键字,可以修饰代码块、方法,自动获取、释放锁

  • ReentrantLock:类,只能对某段代码修饰,需要手动的进行加锁和释放锁

  1. 原子类

提供一些原子类,在低并发情况下使用,是一种无锁的实现,采用CAS机制

public class Atomic {//private  static AtomicInteger atomicInteger = new AtomicInteger(0);private volatile static int num=0;public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(){@Overridepublic void run() {System.out.println(num++);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//System.out.println("线程"+(num++));//分为3条指令执行}}.start();}}
}

这样会造成线程非顺序输出

public class Atomic {private  static AtomicInteger atomicInteger = new AtomicInteger(0);
//   / private volatile static int num=0;public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(){@Overridepublic void run() {System.out.println(atomicInteger.incrementAndGet());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//System.out.println("线程"+(num++));//分为3条指令执行}}.start();}}
}

利用了原子类就解决了刚才的问题

自旋思想(例如乐观锁)

  1. 第一次获取内存值到工作内存中,存储起来作为预期值

  1. 然后对对象数据进行修改

  1. 将工作内存中值写入到主内存,在写入之前需要做一个判断,用预期值与主内存中的值进行比较

  1. 如果预期值与主内存值一致,说明没有其他线程进行修改,将更新的数据写入到主内存中

  1. 如果预期值与主内存中的值不一样,说明其他线程修改了主内存中的值,这时就需要重复操作整个过程

但是这样又会产生一个新的问题俗称(ABA

ABA 问题,即某个线程将内存值由 A 改为了 B,再由 B 改为了 A。当另外一个线程使用预期值去判断时,预期值与内存值相同,当前线程的 CAS 操作无法分辨当前 V 值是否发生过变化。

public class AtomicABA {public static void main(String[] args) throws InterruptedException {AtomicInteger atomicInteger = new AtomicInteger(100);//默认值为100new Thread(() -> {System.out.println(atomicInteger.compareAndSet(100, 101));//设置预期值是100  修改值为101System.out.println(atomicInteger.get());System.out.println(atomicInteger.compareAndSet(101, 100));//设置预期值是101  修改值为100System.out.println(atomicInteger.get());}).start();Thread.sleep(1000);new Thread(() -> {System.out.println(atomicInteger.compareAndSet(100, 101));//返回true 说明修改成功  发生ABA问题System.out.println(atomicInteger.get());}).start();}
}
import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicABA1 {public static void main(String[] args) throws InterruptedException {AtomicStampedReference stampedReference = new AtomicStampedReference(100, 0);new Thread(() -> {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp() + 1);stampedReference.compareAndSet(101,100,stampedReference.getStamp(), stampedReference.getStamp() + 1);}).start();new Thread(() -> {int stamp = stampedReference.getStamp();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}boolean result = stampedReference.compareAndSet(100, 101, stamp, stamp + 1);System.out.println("  >>> 修改 stampedReference :: %s "+result);}).start();}}

CAS缺点:超多线程一同访问时,不停自旋判断,导致cpu占用率高.


http://www.ppmy.cn/news/22672.html

相关文章

Leetcode:416. 分割等和子集、1049. 最后一块石头的重量 II(C++)

目录 416. 分割等和子集 问题描述&#xff1a; 实现代码与解析&#xff1a; 动态规划&#xff08;01背包问题&#xff09;&#xff1a; 原理思路&#xff1a; 1049. 最后一块石头的重量 II 问题描述&#xff1a; 实现代码与解析&#xff1a; 动态规划&#xff08;01背…

【Leetcode】面试题 08.05. 递归乘法、HJ55 挑7

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 面试题 08.05. 递归乘法 HJ55 挑7 面试题 08.05. 递归乘法 面试题 08.05. 递归乘法 题…

笔记:Space-time Neural Irradiance Fields for Free-Viewpoint Video

论文标题&#xff1a;自由视角的时空神经辐射&#xff08;发光&#xff09;场 创新点 使用RGBD单目视频&#xff08;2.5D&#xff09;表示时空。引入对场景深度的监督解决运动模糊问题。 &#xff08;本文仅介绍对NeRF的改进部分&#xff09; 深度重建损失 问题&#xff1…

npm的知识要点

前端开发趋向于分散隔离&#xff0c;多以组件、包的形式来进行。虽然不使用node、npm、webpack、babel等工具依然可以进行前端开发&#xff0c;但这是远离和拒绝新技术、新理念的做法。 npm(node package manage)是基于共享理念的实践、基于node的JavaScript编写的包管理工具&a…

字节跳动抖音推荐算法实习生一面凉经

面试大概50分钟 本来投的是头条开发岗位&#xff0c;不知为何被捞到了推荐算法岗位。多位推荐算法hr一直约我面试&#xff0c;说经历和他们部门契合。我从年底推到年后&#xff0c;最后答应面试&#xff0c;这也是读研以来第一次面试。大概是自己准备不充分&#xff0c;一面就…

模拟(一)回型矩阵、螺旋矩阵

回型矩阵_牛客题霸_牛客网 描述 给你一个整数n&#xff0c;按要求输出n∗n的回型矩阵 输入描述&#xff1a; 输入一行&#xff0c;包含一个整数n 1<n<19 输出描述&#xff1a; 输出n行&#xff0c;每行包含n个正整数. 示例1 输入&#xff1a; 4 输出&#xff1a; 1 2 3 4…

5G NR R16 SPS

一 简介 今天给大家介绍一个R16的小topic&#xff1a;SPS——Semi-persistent Scheduling(半持续调度)&#xff0c;与传统的Dynamic Scheduling(动态调度)相对应。 首先解释什么是SPS&#xff0c;我们知道目前常用的调度方式是动态调度&#xff0c;也就是一个DCI指示一个PDSC…

【内网安全-隧道搭建】内网穿透_Frp上线、测试

目录 Frp&#xff08;简易上线&#xff09; 1、简述&#xff1a; 2、工具&#xff1a; 3、使用&#xff1a; 1、准备&#xff1a; 2、服务端&#xff08;公网&#xff09;&#xff1a; 2、客户端&#xff08;内网&#xff09;&#xff1a; 3、测试方法&#xff1a; 4、…