多线程4

news2024/11/16 3:31:47

死锁

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

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有

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

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

相关文章

Revit模型进入虚幻引擎UE5教程

一、背景 小伙伴们是否有Revit进入虚幻引擎交互的需求呢&#xff1f; 二、实现功能 1.Revit进入虚幻UE5,包含模型属性&#xff0c;材质等 2.实现BIM构件点选&#xff0c;高亮&#xff0c;属性展示 3.实现BIM模型分层显示&#xff0c;爆炸等效果 三、教程地址 教程&#x…

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

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

“Java泛型” 得所憩,落日美酒聊共挥

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能接…

2024 年广东省职业院校技能大赛(高职组)“云计算应用”赛项样题 3

#需要资源&#xff08;软件包及镜像&#xff09;或有问题的&#xff0c;可私聊博主&#xff01;&#xff01;&#xff01; #需要资源&#xff08;软件包及镜像&#xff09;或有问题的&#xff0c;可私聊博主&#xff01;&#xff01;&#xff01; #需要资源&#xff08;软件…

【Easy云盘 | 第十三篇】分享模块(获取目录信息、获取文件信息、创建下载链接)

文章目录 4.4.7获取目录信息4.4.8获取文件信息4.4.9创建下载链接 4.4.7获取目录信息 明天做 4.4.8获取文件信息 明天做 4.4.9创建下载链接 明天做

搜索二维矩阵2 合并两个有序链表

240. 搜索二维矩阵 II - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int i matrix.size() - 1, j 0;while(i > 0 && j < matrix[0].size()){if(matrix[i][j…

第十一届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

第十一届蓝桥杯大赛软件赛省赛C/C 大学 B 组 文章目录 第十一届蓝桥杯大赛软件赛省赛C/C 大学 B 组1、字串排序2、门牌制作3、既约分数4、蛇形填数5、跑步锻炼6、七段码7、成绩统计8、回文日期9、子串分值和10、平面切分 1、字串排序 2、门牌制作 #include<iostream>#def…

HR都关心哪有好用的人才测评工具?

人才测评工具分为两种&#xff0c;一种是测评量表&#xff0c;一种是操作量表的工具&#xff0c;在线测评的方式没有普及之前&#xff0c;很多朋友都习惯把测评量表&#xff08;测评试题&#xff09;称为测评工具&#xff0c;其实我认为量表就是量表&#xff0c;而试试量表测评…

PET-SQL:基于大模型的两阶段Text2SQL方法

简介 PET-SQL出自论文《PET-SQL: A Prompt-enhanced Two-stage Text-to-SQL Framework with Cross-consistency》&#xff0c;将基于大模型的Text2SQL分为两个阶段进行&#xff0c;在第一阶段使用数据表schema信息、数据表采样数据、相似问答问答对生成初步的SQL(PreSQL)&…

linux时间同步工具chrony的配置和时间设置的相关说明

目录 目录 介绍 1.搭建ntp服务器 2.配置ntp客户端 3.其他设置 4.客户端无法进行时间同步 介绍 目前比较流行的时间同步工具有ntpd和chrony&#xff0c;ntpd采用123/UDP端口通信&#xff0c;chrony采用323/UDP端口通信。Centos7以上版本默认安装chrony服务来同步时间&#x…

Agent相关概念(更新中)

文章目录 Agent是什么Agent中要求LLM需要具备哪些能力Function Call&#xff08;工具调用&#xff09;Plan&#xff08;规划&#xff09;memory推理&#xff08;总结&#xff09;能力 Agent的实现方式PlanAndExecuteReact Agent是什么 在LLM语境下&#xff0c;Agent可以理解为…

带头双向循环链表实现

1.结构及特性 前面我们实现了无头单向非循环链表&#xff0c;它的结构是这样的&#xff1a; 在这里的head只是一个指向头结点的指针&#xff0c;而不是带头链表的头节点。 而带头双向循环链表的逻辑结构则是这样的 这就是链表的结构&#xff0c;链表的每一个节点都有两个指针…

[dvwa] Command Injection

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

【C语言】汉诺塔问题

目录 一、何为汉诺塔问题&#xff1f; 二、汉诺塔计算规律 三、打印汉诺塔的移动路径 总结 一、何为汉诺塔问题&#xff1f; 汉诺塔问题是一个经典的问题。汉诺塔&#xff08;Hanoi Tower&#xff09;&#xff0c;又称河内塔&#xff0c;源于印度一个古老传说。大梵天创造世…

matrix-breakout-2-morpheus 靶机渗透

信息收集&#xff1a; 1.nmap存活探测&#xff1a; nmap -sn -r 192.168.10.1/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-06 12:13 CST Nmap scan report for 192.168.10.1 Host is up (0.00056s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap…

Chapter 1 Basic Concepts of Communication and Communication Systems

1.1 The Concept of Communication communication【通信】:It is the process of using signals to transmit messages containing information in space. To put it simply, communication is the spatial transmission of information【信息的空间传递】Information【信息】…

C#速览入门

C# & .NET C# 程序在 .NET 上运行&#xff0c;而 .NET 是名为公共语言运行时 (CLR) 的虚执行系统和一组类库。 CLR 是 Microsoft 对公共语言基础结构 (CLI) 国际标准的实现。 CLI 是创建执行和开发环境的基础&#xff0c;语言和库可以在其中无缝地协同工作。 用 C# 编写的…

Steam上线真人乙游,女性玩家还愿意买单吗?

Steam上线了一款真人乙游《糟糕&#xff01;他们太爱我了怎么办&#xff1f;》&#xff08;以下简称《糟糕&#xff01;&#xff09;。 乍一听这个游戏名&#xff0c;似乎和《完蛋&#xff01;我被美女包围了&#xff01;》有异曲同工之妙&#xff0c;事实也确实如此&#xff…

Python常用算法--解决数据结构问题【附源码】

一、约瑟夫环问题 解释:约瑟夫环(Josephus Problem)是一个著名的数学问题,它描述了一个关于围坐一圈的人进行游戏的场景。游戏规则是从一个人开始,顺序报数,每报到特定数目的人将会被排除出圈子,然后从被排除的下一人开始继续报数,游戏继续进行直到最后剩下一个人。 …

Sharding Sphere JDBC使用Mybatis的saveBatch无法返回主键的问题

问题背景 项目中使用了MybatisPlus框架&#xff0c;数据库是PostgreSQL&#xff0c;配置了主键自增&#xff0c;新增数据后返回主键到实体类中。 项目中因为数据量问题&#xff0c;需要用到分库分表&#xff0c;因此引入了Sharding Sphere JDBC框架。但是Sharding Sphere JDB…