多线程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好的这个而不是重新new
        return  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好的这个而不是重新new
        if (instance == null) {

            synchronized (object) {
                instance = new SingletonLazy();
            }
            return instance;
        }
        //要禁止外部代码来创建该类的实例~~
        private SingletonLazy() {
//类之外的代码,尝试new的时候,,势必就要调用构造方法由于构造方法私有的.无法调用,就会编译出错!!
        }


    }
}

 

 public static SingletonLazy getInstance() {//就可以通过getlnstance来获取已经new好的这个而不是重新new
        synchronized (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;//3

    public static Object object = new Object();


    public static SingletonLazy getInstance() {
        if (instance == null) {//2
            synchronized (object) {//1
                if (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有

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[AI in sec]-039 DNS隐蔽信道的检测-特征构建

DNS隐蔽信道是什么 DCC是指利用DNS数据包中的可定义字段秘密传递信息的通道。其中,“DNS 协议”是目前网络上使用的标准域名解析协议;“可定义字段”是DNS 数据包中的 QNAME 字段、RDATA 字段及RawUDP字段。利用DNS数据包可以构建2种信道:存储信道及时间信道。DCC可以被用于…

【GameFi】 Brilliantcrypto点火活动

活动&#xff1a;https://app.galxe.com/quest/brilliantcrypto/GCt8wthq2J Brilliantcrypto点火活动正在Galxe上进行 &#x1f389; 活动时间&#xff1a;2024/04/06 12:00 ~ 2024/05/04 12:00 奖励总价值1200美元的MATIC 完成任务並在Brilliantcrypto Galxe Space上赚取积分。…

[dvwa] Command Injection

命令注入 0x01 low 没有过滤&#xff0c;直接利用 127.0.0.1 && ip a 函数 php_uname(mode) 动态地检查服务器的操作系统 ‘s’&#xff1a;操作系统名称 ‘n’&#xff1a;网络主机名 ‘r’&#xff1a;操作系统发行版本号 ‘v’&#xff1a;操作系统版本 ‘m’&…

CleanMyMac有必要购买吗?有哪些功能

作为一位产品营销专家&#xff0c;对各类软件产品的功能和特点都有深入的研究&#xff0c;对于CleanMyMac这款产品也有深入了解。CleanMyMac是一款专为Mac用户设计的系统清理与优化软件&#xff0c;旨在帮助用户解决Mac电脑使用过程中的各种问题&#xff0c;让电脑恢复如新的状…

12、最小覆盖子串

如何想到这个解法 问题的特点&#xff1a; 首先&#xff0c;认识到这是一个关于子串的问题&#xff0c;而且需要考虑子串的最小长度。这提示我们可能需要使用一种方式来逐步探索不同的子串。滑动窗口的适用性&#xff1a;滑动窗口是处理子串问题的常用技巧&#xff0c;特别是当…

【系统架构师】-软件架构设计

1、软件架构的概念 架构的本质 1、软件架构为软件系统提供了一个结构、行为和属性的高级抽象。 2、软件架构风格是特定应用领域的惯用模式&#xff0c;架构定义一个词汇表和一组约束。 架构的作用 1、软件架构是项目干系人进行交流的手段。 2、软件架构是可传递和可复用的模型…

ESP32S3网络编程学习笔记(1)—— Wi-Fi扫描实验

前言 &#xff08;1&#xff09;如果有嵌入式企业需要招聘湖南区域日常实习生&#xff0c;任何区域的暑假Linux驱动/单片机/RTOS的实习岗位&#xff0c;可C站直接私聊&#xff0c;或者邮件&#xff1a;zhangyixu02gmail.com&#xff0c;此消息至2025年1月1日前均有效 &#xff…

深澜计费管理系统 任意文件读取漏洞复现

0x01 产品简介 深澜计费管理系统是是一套完善的领先的具有复杂生物型特征的弹性认证计费系统。系统主要由 AAA 认证计费平台、系统运营维护管理平台、用户及策略管理平台、用户自助服务平台、智能客户端模块、消息推送模块、数据统计模块组成。目前在全球为超过 2500 家客户提…

Elastic AI Assistant for Observability 和 Microsoft Azure OpenAI 入门

作者&#xff1a;来自 Elastic Jonathan Simon 最近&#xff0c;Elastic 宣布 AI 观测助手现已正式向所有 Elastic 用户开放。该 AI 观测助手为 Elastic 观测提供了一种新工具&#xff0c;提供了大型语言模型&#xff08;LLM&#xff09;连接的聊天和上下文洞察&#xff0c;以解…

node res.end返回json格式数据

使用 Node.js 内置 http 模块的createServer()方法创建一个新的HTTP服务器并返回json数据&#xff0c;代码如下&#xff1a; const http require(http);const hostname 127.0.0.1; const port 3000;const data [{ name: 测试1号, index: 0 },{ name: 测试2号, index: 1 },…

优秀企业都在用的企微知识库,再不搭建就晚了!

每个团队都在寻找让工作效率提升的方法。如果你想知道哪些团队能够高效地完成任务&#xff0c;而另一些却步履维艰&#xff0c;那么答案可能就是“企业微信知识库”。见过很多团队都在使用它&#xff0c;而且效果非常显著。如果你还没有搭建属于自己的企微知识库&#xff0c;可…

1.c++入门(命名空间、缺省参数、函数重载、引用、内联函数、for循环、auto关键字、指针空值nullptr)

1.c的第一个程序 // 方法一 #include<iostream>// namespace为命名空间的关键字&#xff0c;std为空间名&#xff1b; C标准库的东西放进std命名空间 using namespace std; int main() {cout << "hello world" << endl;return 0; }// 方法二 #in…

Python标准数据类型—字符串常用方法

在Python中&#xff0c;字符串是一种常见的数据类型&#xff0c;用于表示文本信息。Python提供了许多内置方法来处理字符串&#xff0c;使得对文本的操作变得非常方便。在本篇博客中&#xff0c;我们将介绍一些常用的字符串方法&#xff0c;帮助您更好地理解和利用字符串。 &a…

基于java web的超市管理系统

摘要 随着社会经济的不断发展&#xff0c;人们的生活水平不断提高。越来越多的零售行业得到了快速的发展&#xff0c;以最常见的超市最为明显。零售行业繁荣的背后也随之带来了许多行业隐患&#xff0c;越来越激烈的行业竞争不断的要求经营者更加高要求的管理超市内部的整个供…

4.7学习总结

java学习 一.Stream流 (一.)概念: Stream将要处理的元素集合看作一种流&#xff0c;在流的过程中&#xff0c;借助Stream API对流中的元素进行操作&#xff0c;比如&#xff1a;筛选、排序、聚合等。Stream流是对集合&#xff08;Collection&#xff09;对象功能的增强&…

算法 第34天 贪心3

1005 K 次取反后最大化的数组和 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&#xff0c;返回数组 可能…

JavaEE——手把手教你实现简单的 servlet 项目

文章目录 一、什么是 Servlet二、创建一个简单的 Servlet 程序1. 创建项目2.引入依赖3. 创建目录4.编写代码5. 打包程序6. 部署7.验证整体过程总结 三、使用 Smart Tomcat 插件简化项目创建四、创建项目时可能遇到的几个问题。 一、什么是 Servlet Servlet 是一种实现 动态页面…

Bigtable [OSDI‘06] 论文阅读笔记

原论文&#xff1a;Bigtable: A Distributed Storage System for Structured Data (OSDI’06) 1. Introduction Bigtable 是一种用于管理结构化数据的分布式存储系统&#xff0c;可扩展到非常大的规模&#xff1a;数千台服务器上的数据量可达 PB 级别&#xff0c;同时保证可靠…

苍穹外卖Day10——总结10

前期文章 文章标题地址苍穹外卖Day01——总结1https://lushimeng.blog.csdn.net/article/details/135466359苍穹外卖Day02——总结2https://lushimeng.blog.csdn.net/article/details/135484126苍穹外卖Day03——总结3https://blog.csdn.net/qq_43751200/article/details/1363…

在 K8s 上跑腾讯云 Serverless 函数,打破传统方式造就新变革

目录 目录 前言 Serverless 和 K8s 的优势 1、关于Serverless 函数的特点 2、K8s 的特点 腾讯云 Serverless 函数在 K8s 上的应用对企业服务的影响 1、弹性扩展和高可用性 2、成本优化和资源利用 3、简化部署和管理 拓展&#xff1a;腾讯云云函数 SCF on K8s 番外篇…