多线程4

死锁

  想获取到第二把锁,就需要执行完第一层大括号,想要执行完第一层大括号,就要先获取到第二层的锁。

synchronized (counter2){
synchronized (counter2){}
}

例子:t2先启动,t2进行加锁后一定成功,但是如果t2进行二次加锁的时候因为counter2已经被锁定了,所以他需要外层大括号的counter2进行解锁,但是这又是加锁操作,所以就会一直阻塞等待,于是就矛盾了,产生了对峙的画面(狗咬狗不松口)。

"引用计数"

可重入锁,防止程序员搞成死锁。

如何判定,当前遇到的}是最外层的}??JVM 是咋知道的??

更简单的办法,就是给锁对象里也维护一个计数器

每次{n++,每次遇到},n--。

就相当于当自己的锁给自己的锁加锁的时候就会形成嵌套锁的时候,就会防止形成嵌套锁的情况,

synchronized不存在问题,idea没必要提示~~

死锁的场景

 场景一:锁是不可重入锁,并且一个线程针对一个锁对象,连续加锁两次通过引入可重入锁,问题就迎刃而解了,九月场景一是锁不住的对吧

场景二:两个线程两把锁

有线程1和线程2,以及有锁A和锁B

现在,线程1和2 都需要获取到锁A和锁B

拿到锁A之后,不释放A,继续获取锁B

先让两个线程分别拿到一把锁,然后在去尝试获取对方的锁

public class Test2 {public static void main(String[] args) throws InterruptedException {Object o1= new Object();Object o2= new Object();Thread thread1=new Thread(()->{synchronized (o1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o2){System.out.println("两把锁");}}});Thread thread2=new Thread(()->{
synchronized (o2){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o1){System.out.println("两把锁");}
}});thread2.start();thread1.start();thread1.join();thread2.join();System.out.println("结束");}}

这会产生死锁状态。

场景三:N个线程,M把锁

死锁的四个必要条件 !!!

1.锁具有互斥特性.(基本特点,一个线程拿到锁之后,其他线程就得阻塞等待)

2.锁不可抢占(不可被剥夺) 一个线程拿到锁之后,除非他自己主动释放锁,否则别人抢不走~~

3. 请求和保持,一个线程拿到一把锁之后,不释放这个锁的前提下,再尝试获取其他锁!

4.循环等待.  多个线程获取多个锁的过程中,出现了循环等待. A 等待 B,B 又等待 A.

必要条件:缺一不可任何一个死锁的场景,都必须同时具备上述四点只要缺少一个,都不会构成死锁

public class Test2 {public static void main(String[] args) throws InterruptedException {Object o1= new Object();Object o2= new Object();Thread thread1=new Thread(()->{synchronized (o1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o2){System.out.println("两把锁");}}});Thread thread2=new Thread(()->{
synchronized (o1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o2){System.out.println("两把锁");}
}});thread2.start();thread1.start();thread1.join();thread2.join();System.out.println("结束");}}

约定每个哲学家,必须先获取编号小的筷子,后获取编号大的筷子~
 

内存可见性

import java.util.Scanner;public class Test3 {public static int   count=0;public static void main(String[] args) {Thread thread=new Thread(()->{while (count==0){}System.out.println("t1 执行结束");});Thread thread1=new Thread(()->{Scanner scanner=new Scanner(System.in);count=scanner.nextInt();});thread1.start();thread.start();}
}
import java.util.Scanner;public class Test3 {public static int   count=0;public static void main(String[] args) {Thread thread=new Thread(()->{while (count==0){}});Thread thread1=new Thread(()->{Scanner scanner=new Scanner(System.in);count=scanner.nextInt();});thread1.start();thread.start();}
}

代码转换为下面的时候就会一直进入无限循环,不能跳出循环。

由于系统自带简化

while (count==0){}
会load从内存读取数据到cpu寄存器cmp(比较,同时会产生跳转)条件成立,继续顺序执行条件不成立,就跳转到另外一个地址来执行。

但是循环过快的时候,load循环速度慢,执行load的时间使上万次的cmp执行效率。

所以就把load优化掉了,专业昂就不会进行判断,这样就会使效率提高

上述问题本质上还是编译器优化引起的.优化掉load操作之后,使t2线程的修改,没有被t1线程感知到“内存可见性”问题

volatile

是告诉编译器,不要触发上述优化

如何解决上述内存可见性问题??就内存可见性问题来说,可以通过特殊的方式来控制,不让它触发优化的volatile关键字。

volatile是专门针对内存可见性的场景来解决问题的,并不能解决之前,两个线程循环count++的问题

引l入synchronized其实是因为加锁操作本身太重量了.相比于load来说,开销更大,编译器自然就不会对load优化了.(和加上sleep/io操作)

当t1执行的时候,要从工作内存中读取count的值,而不是从主内存中.后续t2修改count,也是会先修改工作内存,同步拷贝到主内存.但是由于t1没有重新读取主内存,导致最终t1没有感知到t2的修改.

 线程等待通知机制

系统内部,线程是抢占式执行,随机调度.程序员也是有手段干预的.通过“等待”的方式,能够让线程一定程度的按照咱们预期的顺序来执行。

例子:在ATM机取钱的时候,如果1号去取钱,但是ATM正好没钱,这时候2号再进去取钱,就会产生频繁的没有意义的系统调度,cpu永远在做无效的工作,就会影响效率。

这时候就需要应用线程等待的方法,查看当前的逻辑是否能执行,如果不能执行就主动wait,避免造成无效工作,等待后续时机成熟(ATM有人存钱了),阻塞就会自动被唤醒

   synchronized (object){try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}

wait使先解锁然后再阻塞等待,其他的线程就可以获取到object这个锁,防止了死锁的产生。

import java.util.Scanner;public class Test4 {public static void main(String[] args) {Object o=new Object();Thread thread=new Thread(()->{synchronized (o){try {o.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread thread1=new Thread(()->{Scanner scanner=new Scanner(System.in);synchronized (o) {scanner.nextInt();o.notify();}});thread.start();thread1.start();}}

先解除再堵塞等待,可以通过notiify来唤醒。

唤醒后不会立刻执行从WAITING-----RUNNABLE---BLOCKED

但是没有规定执行顺序的时候很有可能会导致,thread1先执行,最后导致notify产生不了效果

 o.notifyAll();//唤醒全部等待的线程

多线程代码

1.单例模式

单例模式是一个经典的设计模式。

单例模式--》单个实例,instance就是对象。

整个过程中的某个类,有且只有一个对象(不会再new出来新的对象)

饿汉模式

只要运行就会立刻instance,无论后面用不用都会调用

public class Singleton {private  static Singleton instance=new Singleton();//只要启动就会立刻生成给instance这个对象public  static Singleton getInstance(){//就可以通过getlnstance来获取已经new好的这个而不是重新newreturn  instance;}//要禁止外部代码来创建该类的实例~~private  Singleton(){
//类之外的代码,尝试new的时候,,势必就要调用构造方法由于构造方法私有的.无法调用,就会编译出错!!}
}

懒汉模式

计算机中,谈到懒,往往是一个"褒义词",而且是“高效率”的代表

懒汉模式,不是在程序启动的时候创建实例,而是在第一次使用的时候才去创建(如果不使用了,就会把创建实例的代价就节省下来了)

public class SingletonLazy {private  static SingletonLazy instance=null;public  static SingletonLazy getInstance(){if (instance==null){instance=new SingletonLazy();}
return instance;}private  SingletonLazy(){//重中之重}}

在只考虑一个方法getInsttance方法的情况下考虑:

饿汉模式是否安全?安全=>1

创建实例的时机是在java进程启动(比main调用还早的时机)

懒汉模式是否安全?不安全

 t2切换回来之后还是要进行新的new对象操作,就会产生多个对象了。

Instance中的地址指向的那个对象,,可就是一个大的对象了。

产生的对象会被覆盖但是产生以及浪费的时间可是真金白银。

添加

synchronized锁操作。
public class SingletonLazy {public static Object object = new Object();public static void main(String[] args) {}private static SingletonLazy instance = null;//只要启动就会立刻生成给instance这个对象public static SingletonLazy getInstance() {//就可以通过getlnstance来获取已经new好的这个而不是重新newif (instance == null) {synchronized (object) {instance = new SingletonLazy();}return instance;}//要禁止外部代码来创建该类的实例~~private SingletonLazy() {
//类之外的代码,尝试new的时候,,势必就要调用构造方法由于构造方法私有的.无法调用,就会编译出错!!}}
}

 

 public static SingletonLazy getInstance() {//就可以通过getlnstance来获取已经new好的这个而不是重新newsynchronized (object) {if (instance == null) {instance = new SingletonLazy();}return instance;}

针对后续调用,明明没有线程安全问题,还要加锁,就是画蛇添足加锁本身,也是有开销的=>可能会使线程阻塞)

所以要进行优化操作。

        

StringBuilder 不带锁

StringBuffer 带锁

public static SingletonLazy getInstance() {if (instance==null) {//判定是否要加锁实例化之后,线程自然安全了,就无需加锁了实例化之前,new之前,就应该要加锁synchronized (object) {//在这俩if之间,synchronized会使该线程阻塞,阻塞过程中其他线程就可能会修改Instance的值if (instance == null) {//判定是否要创建对象instance = new SingletonLazy();}return instance;}private SingletonLazy() {}}}

此外还要加上volatile这样防止他进行优化操作。 

t1线程修改了Instance引l用,t2有可能读不到.(概率应该是比较小).加上volatile主要是为了万无一失.

指令重排序

加了volatile也能够解决指令重排序引l起的线程安全问题

调整顺序最主要的目的就是提高效率.(前提是保证逻辑是等价的)

重排序的前提,一定是重新排序之后,逻辑和之前等价单线程下,编译器进行指令重排序的操作,一般都是没问题的.编译器可以准确的识别出,哪些操作可以重排序,而不会影响到逻辑~~

instance=new SingletonLazy

这一行代码,其实还可以简要细分成三个步骤~

例子

1.买了个房子)2.装修3.拿到钥匙

123(精装房,开发商直接给你装修好,你收房的时候,已经装修完了

132(毛坏房,自己装修)

1.申请内存空间

2.调用构造方法.(对内存空间进行初始化)

3.把此时内存空间的地址,赋值给Instance引用

在指令重排序优化策略下,上述执行的过程不一定是123也可能是132(1一定是先执行的)

如果这样执行1.申请内存3把地址赋值给引用

一旦执行完意味着Instance就非null !!但是指向的对象其实是一个未初始化的对象(里面的成员都是0)这样就会返回没有初始化的对象。

如果在执行其他方法的时候就会出现没有初始化的对象在操作会产生非常严重的问题/

2.调用方法

所以要引用volatile

按照加上volatile之后,此时,t2线程读到的数据,一定是t1已经构造完毕的完整对象了.(一定是123都执行完毕的对象)

public class SingletonLazy {private static volatile SingletonLazy instance = null;//3public static Object object = new Object();public static SingletonLazy getInstance() {if (instance == null) {//2synchronized (object) {//1if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}

2.阻塞队列

0.普通队列线程不安全的

1.优先级队列.

2.阻塞队列先进先出,线程安全,并且带有阻塞功能-------》1.队列为空,尝试出队列,出队列操作就会阻塞一直阻塞到队列不空为止2.队列为满,尝试入队列,入队列操作也会阻塞一直阻塞到队列不满为止.

3.消息队列不是普通的先进先出,而是通过topic(一块)这样的参数来对数据进行归类出队列的时候,指定topic,每个topic下的数据是先进先出的.

生产者消费者模型

在开发中主要又有两方面的意义

1.能够让程序进行解耦

举个包饺子的例子:

中间的盖帘,相当于一个阻塞队列/消息队列

如果师娘擀的慢,我俩包的快,此时,盖帘上就空着了我和小汤就会阻塞等待如果我俩包的慢,师娘擀的快~~很快盖帘放满了师娘就要阻塞等待。

如果让A直接调用B意味着A的代码中就要包含很多和B相关的逻辑B的代码中也会包含和A相关的逻辑彼此之间就有了一定的耦合。

一旦对A做出修改,可能就会影响到B反之亦然一旦A出bug,也容易把B牵连到反之也是亦然。

 站在A的视角,不知道B的存在,只关心和队列的交互站在B的视角,不知道A的存在,只关心和队列的交互此时,对A的修改,就不太容易影响到BA如果挂了,也不会影响到B。

2.能够使程序"削峰填谷"

客户端发来的请求,个数多少,没法提前预知.遇到某些突发事件,就可能会导致客户端给服务器的请求激增~~

如果是这样的话,b要进行许多重量级操作,一旦A收到的请求增加了,B的请求也会增加,A做的工作简单,B做的复杂,这样B就容易崩溃。

 增加了mq后无论A给队列写多快,B都可以按照固有的节奏来消费数据B的节奏,就不一定完全跟着A了.相当于队列把B保护起来了。

阻塞队列,

BlockingQueue<E>

 阻塞队列只需要考虑,入队列和出队列即可.阻塞队列没有“取队首元素”操作.(也不是完全没有,只不过是没有阻塞功能)

其中的offer和put没有阻塞功能,但是put和take有

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

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

相关文章

图数据库技术:知识图谱的存储与查询

图数据库技术&#xff1a;知识图谱的存储与查询 一、引言 在探索知识的宇宙中&#xff0c;知识图谱是组织和理解海量信息的星系图。在这张图中&#xff0c;每一个概念、实体与事物不再是孤立的点&#xff0c;而是通过关系与边相互连接&#xff0c;形成一个复杂而有机的网络。图…

计算机网络练习-计算机网络概述与性能指标

计算机网络概述 ----------------------------------------------------------------------------------------------------------------------------- 1. 计算机网络最据本的功能的是( )。 1,差错控制 Ⅱ.路由选择 Ⅲ,分布式处理 IV.传输控制 …

3.网络编程-TCP

目录 TCP 建立连接的过程是怎样的 TCP为什么是三次握手 TCP 断开连接的过程是怎样的 TCP挥手为什么需要四次 为什么TIME_WAIT等待的时间是2MSL TCP详解之滑动窗口 TCP 半连接队列和全连接队列是什么 TCP粘包&#xff0c;拆包是怎么发生的&#xff0c;如何解决 TCP是如何…

书生·浦语大模型实战营之茴香豆:搭建你的 RAG 智能助理

书生浦语大模型实战营之茴香豆&#xff1a;搭建你的 RAG 智能助理 RAG&#xff08;Retrieval Augmented Generation&#xff09;技术&#xff0c;通过检索与用户输入相关的信息&#xff0c;并结合外部知识库来生成更准确、更丰富的回答。解决 LLMs 在处理知识密集型任务时可能遇…

高项-进度管理

成本管理就是要确保项目在批准的预算内完成。 成本的类型 成本的组成 项目成本管理储备成本基准&#xff08;需要经过批准才能进行变更&#xff09; 成本基准应急储备工作包成本&#xff08;在基准内的可以不经过批准变更&#xff09; 工作包成本活动成本活动应急储备&…

物联网实战--驱动篇之(三)LoRa(sx1278)

目录 一、LoRa简介 二、sx1278模块 三、硬件抽象层 四、SX1278初始化 五、发送时间计算 六、发送模式 七、接收模式 八、总结 一、LoRa简介 LoRa在物联网传输领域有着举足轻重的地位&#xff0c;平时大家可能比较少听说&#xff0c;因为它主要还是在行业应用&#xff0…

C语言整数和小数的存储

1.整数在内存中的存储 计算机使用二进制进行存储、运算&#xff0c;整数在内存中存储使用的是二进制补码 1.1原码、反码、补码 整数的2进制表⽰⽅法有三种&#xff0c;即 原码、反码和补码 三种表⽰⽅法均有符号位和数值位两部分&#xff0c;符号位都是⽤0表⽰“正”&am…

鸿蒙内核源码分析 (Fork 篇) | 一次调用,两次返回

第一次看到 fork 时&#xff0c;说是一次调用&#xff0c;两次返回&#xff0c;当时就懵圈了&#xff0c;多新鲜&#xff0c;真的很难理解。因为这足以颠覆了以往对函数的认知&#xff0c; 函数调用还能这么玩&#xff0c;父进程调用一次&#xff0c;父子进程各返回一次。而且只…

记Postman参数化

因为需要在WEB页面上处理部分数据&#xff0c;手动操作太慢&#xff0c;所以考虑使用接口方式处理&#xff0c;因急于使用&#xff0c;用Python Request的方式&#xff0c;写代码也来得慢&#xff0c;故采用Postman加外部文件参数化方式来实现。 接口请求是Post方式&#xff0c…

电商平台混战之下,天猫破解品牌增长奥秘

行业共识是追上风&#xff0c;才有好生意&#xff0c;但风很多时候不会只有一个方向。 4月2日&#xff0c;上海&#xff0c;TopTalk 2024天猫超级品牌私享会举行。这个活动已举办数年&#xff0c;每一年天猫都会发布新一年度的品牌经营策略&#xff0c;只是与往年不同的是&…

YOLOv9改进策略 :原创自研 | 自研MSAM注意力,通道注意力升级,魔改CBAM

💡💡💡本文自研创新改进:MSAM(CBAM升级版):通道注意力具备多尺度性能,多分支深度卷积更好的提取多尺度特征,最后高效结合空间注意力 1)作为注意力MSAM使用; 推荐指数:五星 MSCA | 亲测在多个数据集能够实现涨点,对标CBAM。 改进1结构图如下: 《YOLOv…

linux安全加固

1.登录账号加固 /etc/login.defs 创建⽤户的默认设置⽂件 grep -Ev "^#|^$" /etc/login.defs /etc/login.defs ⽂件⽤于在创建⽤户时&#xff0c;对⽤户的⼀些基本属性做默认设置&#xff0c;例如指定⽤户 UID 和 GID 的范围&#xff0c;⽤户的过期时间&#xff0…

寻找排序数组中的最小值

题目描述 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到&#xff1a; 若旋转 4 次&#xff0c;则可以得到 [4,5,6,7,0,1,2]若旋转 7 次…

【前端】CSS(引入方式+选择器+常用元素属性+盒模型+弹性布局)

文章目录 CSS一、什么是CSS二、语法规范三、引入方式1.内部样式表2.行内样式表3.外部样式 四、选择器1.选择器的种类1.基础选择器&#xff1a;单个选择器构成的1.标签选择器2.类选择器3.id 选择器4.通配符选择器 2.复合选择器1.后代选择器2.子选择器3.并集选择器4.伪类选择器 五…

Linux 内核优化简笔 - 高并发的系统

简介 Linux 服务器在高并发场景下&#xff0c;默认的内核参数无法利用现有硬件&#xff0c;造成软件崩溃、卡顿、性能瓶颈。 当然&#xff0c;修改参数只是让Linux更好软件的去利用已有的硬件资源&#xff0c;如果硬件资源不够也无法解决问题的。而且当硬件资源不足的时候&am…

AcWing 788. 逆序对的数量——算法基础课题解

AcWing 788. 逆序对的数量 题目描述 给定一个长度为 n 的整数数列&#xff0c;请你计算数列中的逆序对的数量。 逆序对的定义如下&#xff1a;对于数列的第 i 个和第 j 个元素&#xff0c;如果满足 i<j且 a[i]>a[j]&#xff0c;则其为一个逆序对&#xff1b;否则不是。…

Cute Background FX

Cute Background FX是环境背景粒子系统的集合。非常适合作为菜单的背景。 该包包括: -20个独特预制件+20个URP预制件 -5种独特的环境设计 -15种纹理 -2个自定义着色器+2个URP着色器 -共59项独特资产 -一个演示场景,您可以在其中概述所有内容。 所有纹理都是512x512分辨率的P…

基于SSM框架实现的在线心理评测与咨询系统(技术栈 spring+springmvc+mybatis+jsp+jquery+css)

一、项目简介 本项目是一套基于SSM框架实现的在线心理评测与咨询系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&am…

iOS 应用内网络请求设置代理

主要通过URLSessionConfiguration 的connectionProxyDictionary 属性 为了方便其他同学使用&#xff0c;我们可以通过界面来进行设定&#xff08;是否开启代理、服务端、端口&#xff09;&#xff0c;从而达到类似系统上的设定 具体链接参考&#xff1a;为 iOS 网络请求设置代理…

设计模式总结-桥接模式

桥接模式 模式动机模式定义模式结构模式分析桥接模式实例与解析实例一&#xff1a;模拟毛笔 模式优缺点 模式动机 设想如果要绘制矩形、圆形、椭圆、正方形&#xff0c;我们至少需要4个形状类&#xff0c;但是如果绘制的图形需要具有不同的颜色&#xff0c;如红色、绿色、蓝色…