JAVAEE-多线程(2)

news2024/9/27 23:27:47

线程安全

线程安全的意思技术在多线程的各种随机调度顺序下,代码没有bug,都能够符合预期的方式来执行

线程为什么会不安全?就是在多线程随机调度下出代码出现bug。

有些代码在多线程环境下执行会出现bug,这样的问题就叫做线程不安全。

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-02
 * Time: 17:04
 */

//演示线程安全问题

class Counter{
    public int count = 0;

    public  void increase(){
        count++;
    }
}
public class L104 {
    private static Counter counter = new Counter();

    public static void main(String[] args) throws InterruptedException {
    //两个线程对counter进行五万次自增,预期结果十万
        Thread t1= new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2= new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("counter: " + counter.count);
    }


}

而运行结果可能就不等于十万,原因是进行的count++操作,底层是三条指令在CPU上完成的

先从内存的数据读取到CPU寄存器中, load

如何把CPU的寄存器中的值进行+1,    add

最后把寄存器中的值写回到内存中。     save

当前是两个线程修改一个变量,每次修改是三个步骤(也就是不是原子的),由于线程之间的调度书顺序不确定,所以两个线程在真正执行这些操作的时候,就有可能有多种执行的排列顺序。

例如

线程t1的load执行了,在执行接下来的add和save时,线程t2的load,add,save就有可能去抢占式执行。因此出现了多种排列组合方式导致不同结果的出现,原因是这些排列组合有些相加等于2有些就不等于。 

不安全的原因有:

1.抢占式执行(内核实现,想要解决无能为力)

多个线程的调度执行过程,可以视为是”全随机的“

因此在多线程代码的时候,我们需要考虑到任意一种调度的情况下,都是能够运行出正确结果的。

2.多个线程修改同一个变量(有的时候可以调整代码,规避线程安全问题,但是普适度不高)

一个线程修改一个变量,多个线程读同一个变量,多个线程修不同变量,以上都没有问题,只有多个线程修改同一个变量时就会出问题。

3.修改操作不是原子的。(解决线程安全问题的主要入手途径——多个操作通过特殊手段打包成一个操作)

count++本质上是三个cpu指令,如果这个指令执行一半就被调度走了我们就说这个修改操作不是原子的。

4.内存可见性问题

JVM代码优化引入的bug,优化就是在同等的效果下去提升效率,单线程下没啥问题。多线程下经常出现误判问题。

5.指令重排序

要怎么让线程安全?

通过特殊手段来让count++变成原子的,这种手段就叫做加锁。

加锁需要在count++之前就进行加锁,在count++之后再解锁。

在加锁和解锁之间,进行修改,这个时候别的线程要进行修改是无法进行的。其他线程只能阻塞等待,也就是处于BLOCK状态)

锁具有独占特性,当前没有人来加锁,加锁操作就可以成功,已经有人加过锁了在前一个锁解锁前都会阻塞等待。

锁可以理解为把并行的两组load add save 变成了并行操作的,因此会一定程度减少执行效率

synchronized
//使用这个关键字来进行加锁操作

 

 这样一来,两个线程自增之和就到了十万。

上图的两个increase会涉及到锁竞争,一方要等另外一方解锁才可加锁成功。但是for循环是在外面的,仍然是并发执行的。 

加锁想要考虑好锁那端代码,锁的代码不一样,对代码执行效果会有很大影响,锁的代码越多,锁的力度越大/越粗,反之越小/越细。

上图的for循环放在了锁里面,这下代码完全串行执行 

线程加锁,不是加了锁就安全,而是通过加锁来让并发修改同一个变量变成串行修改同一个变量才得以安全。

因此在上述案例,也就是count++,如果一个线程加锁,就不会涉及到锁竞争,也就不会阻塞等待,也就没有作用了。

Synchronized关键字

可以修饰方法和代码块和静态方法。

 也可以用这种方法来进行加锁,可以对自己想加锁的代码块来进行选择。

synchronized后面的括号需要天的加锁要针对加锁的对象,也就是锁对象。括号中填成this就是针对当前对象加锁,谁调用就对谁加锁。所以当中国关键字修饰方法时就相当于括号里写了this一样。

Tip;在java中,任意的对象都可以作为锁对象,而在C++或者python,go中,只有特定的对象可以被用于加锁。

出现锁竞争的情况:

1.

 这个时候counter1和counter2就没有锁竞争,因为是两个不同的对象,加锁是针对counter和counter2两个不同对象加锁。、

2.

 上图是针对locker对象进行加锁,locker是counter的一个普通成员,也就是每个countert实例都有自己的locker实例,这个时候因为两个counter是同一个对象,所以两个locker是同一个对象,使用还是会产生锁竞争。

3.

 

counter和counter2有两个不同的locker对象,所以也没有锁竞争。

4.

 此时的locker是一个静态成员(类属性) ,因为一个进程中类属性只有一个,所以虽然对象是counter和counter2,但是还是同一个locker对象,存在锁竞争

5.

 

increase这个线程是在针对静态locker对象进行加锁,而increase2则是在针对这个counter对象本身进行加锁,本身是不同的两个对象,所以也没有锁竞争。

6.

 这个时候锁对象变成了类对象 (简单地说就是描述这个类有啥东西的一个东西),一个jvm进程也就只有一个类对象,所以会存在锁竞争。

死锁问题

 

 上述两组代码都是连续加锁了两次,第一次可以加锁成功,但是第二次加锁就会失败,因为锁已经被占用。这样的锁就是死锁,也叫做不可重入锁。

我们要写一个可重入锁,需要注意:

1.让锁里持有线程而对象,记录是谁加了锁

2.维护一个计数器,用来衡量啥时候真加锁,啥时候真解锁,啥时候直接放行

内存可见性问题

package threading;

import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: admin
 * Date: 2023-01-04
 * Time: 20:07
 */
public class L1043 {
    static class Counter{
        public int count = 0;
    }
    public static void main(String[] args){
        Counter counter = new Counter();

        Thread t = new Thread(()->{
           while (counter.count == 0){

           }
        });
        t.start();

        Thread t2 = new Thread(()->{
            System.out.println("请输入一个数字");
            Scanner scanner = new Scanner(System.in);
            counter.count=scanner.nextInt();
        });
        t2.start();
    }
}

结果并没有结束进程。 

t1重复读取且结果一样,编译器为了提升效率而自动优化成不在循环度内存,读一次就结束了。这个时候t2把内存改了,t1也就没有感知到,这就是内存可见性问题,本质为编译器优化在多线程环境下的误判。

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

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

相关文章

海思嵌入式开发-004-Hi3516烧录问题总结

烧录Hi3516DV300小型系统 问题总结一、前言二、使用USB进行烧录① 前提条件② 操作步骤三、运行镜像文件一、前言 Hi3516DV300的镜像烧录通过Winodow环境进行烧录&#xff0c;开发者启动烧录操作后&#xff0c;DevEco Device Tool通过Remote远程模式&#xff0c;将Ubuntu环境下…

罗胖时间的朋友跨年演讲释放的世界氢燃料电池汽车信号

罗胖时间的朋友2022跨年演讲中关于《故乡与魔法》中讲述了关于氢能源相关的问题&#xff0c;日本在2013年就把发展氢能源定为了国策&#xff0c;在日本&#xff0c;关于氢能源相关的专利占世界30%左右&#xff0c;为什么他们就搞不起来&#xff0c;这是个很好的问题&#xff0c…

04-微服务客户端负载均衡器Ribbon、LoadBalance实战

什么是Ribbon 目前主流的负载方案分为以下两种&#xff1a; 集中式负载均衡&#xff0c;在消费者和服务提供方中间使用独立的代理方式进行负载&#xff0c;有硬件的&#xff08;比如F5&#xff09;&#xff0c;也有软件的&#xff08;比如 Nginx&#xff09;。客户端根据自己的…

同样是血氧仪方案,价格怎么相差这么多?

有没有发现最近血氧仪、额温枪、壁挂式测温仪又开始火了&#xff1f;并且市场活跃度越来越高。而作为我们血氧仪方案提供商或者生产企业来说&#xff0c;您是不是和优优一样会时常听到客户发出如下反馈&#xff1a; “我刚问了另外一家&#xff0c;和你这个样子差不多的&#…

智云通CRM:如何应对“我们还要再考虑一下”?

在征求决策层对报价问题的反馈信息时&#xff0c;销售经常会遇到的另一种意见&#xff08;群组决策中最常见的意见&#xff09;是推迟做出决策。这种情况显然是可以理解的&#xff0c;内部决策层认为有必要先私下讨论一番&#xff0c;然后再对你的报价做出答复。毕竟&#xff0…

P2835 刻录光盘

题目描述 在JSOI2005夏令营快要结束的时候&#xff0c;很多营员提出来要把整个夏令营期间的资料刻录成一张光盘给大家&#xff0c;以便大家回去后继续学习。组委会觉得这个主意不错&#xff01;可是组委会一时没有足够的空光盘&#xff0c;没法保证每个人都能拿到刻录上资料的光…

二、入门案例

文章目录二、入门案例1、开发环境2、创建数据库及表2.1 创建表2.2 添加数据3、创建Spring Boot工程3.1 初始化工程3.2 引入依赖3.3 idea中安装lombok插件4、编写代码4.1 配置application.yml4.2 启动类4.3 添加实体4.4 添加mapper4.5 测试4.6 添加日志【尚硅谷】MyBatisPlus教程…

盘点ERP系统应具备的10个核心功能

ERP系统在数据管理解决方案中名列前茅。对于许多企业来说&#xff0c;ERP是数据分析、自动化和全面基础设施整合的理想解决方案。还不知道如何选择正确的ERP系统解决方案&#xff1f;别担心&#xff0c;本文盘点了ERP系统应具备的10个核心功能。 1. 集成 该ERP功能是该解决方…

怎么录屏幕视频,不要错过这4种简单的电脑录屏方法

屏幕截图和屏幕录制是我们日常生活中常用的一种手段。比如把自己感兴趣的视频录下来和朋友们分享&#xff0c;或者录制操作视频教程让大家知道怎么做。那怎么录屏幕视频&#xff0c;今天在这里小编为大家推荐4种简单的电脑录屏方法&#xff0c;有需要的小伙伴可以往下看看。电脑…

如何学习批判性思维?——以是什么、为什么、怎么样为视角

写这篇文章有两个目的&#xff1a; 希望对研究者做课题有所帮助。希望整个社会能更多地去思考别人的观点到底成不成立。这点是我们很缺乏的。我会列出国内常见的一些逻辑谬论来加以阐述。 &#xff08;图&#xff1a;批判一个观点的统一框架&#xff09; 一、逻辑陈述划分 …

第五届字节青训营笔试后端编程练习题解

文章目录前言T1.36进制加法&#xff08;模拟&#xff09;题面思路代码T2.电影院选座&#xff08;DFS&#xff09;题面思路代码T3.IP地址&#xff08;DFS&#xff09;题面思路代码前言 前段时间&#x1f40f;了&#xff0c;今天简单写了一下&#xff0c;不知道如何提交代码进行…

新年新玩法,数组“招婿”:老许,你要老婆不要?

文章目录前言一、电梯“招婿”启示风波1.1、寻找“一位又高又帅的 IT 同行人员”1.2、数组“招婿”核心代码1.3、代码全是错误&#xff0c;水平不高二、Java 声明、创建、初始化数组2.1、数组声明存在的语法错误2.2、数组声明、创建、初始化的正确方式三、Java 中对于不同数据类…

尚医通-医院详情-科室接口-显示科室前端整合(二十四)

目录&#xff1a; &#xff08;1&#xff09;医院详情-功能 实现 &#xff08;2&#xff09;医院排班-需求分析 &#xff08;3&#xff09;医院排班-科室接口开发 &#xff08;4&#xff09;医院排班-显示科室前端整合 &#xff08;1&#xff09;医院详情-功能 实现 在Ho…

基于KT6368A芯片开发SOP8蓝牙自拍杆方案记录总结

一、简介给客户开发一款蓝牙自拍杆的方案芯片&#xff0c;使用KT6368A单芯片解决。两种实现逻辑1、由客户的遥控MCU来控制蓝牙芯片的开启和关闭 2、由KT6368A蓝牙芯片自己完成所有的事情&#xff0c;分两个按键&#xff0c;一个按键短按开机&#xff0c;短按关机。第二个按键实…

【SPI实验--数码管】

SPI实验--数码管一、SPI概念二、SPI硬件连接三、SPI总线通信协议四、SPI四种通信模式五、代码实现数码管一、SPI概念 1.SPI总线是Motorola首先提出的全双工三线制/四线制同步串行总线 2.采用主从模式&#xff08;Master Slave&#xff09;架构&#xff0c;支持多slave模式应用…

预测2023年Web3的9大发展趋势

Crypto 和 DeFi 在 2022 年深陷熊市&#xff0c;但 Web3 方面的需求刚刚才开始获得关注。在过去的 12 个月里&#xff0c;Web3 相关的搜索与 2021 年底的峰值相比&#xff0c;一直保持在 30% 以内&#xff0c;下一个上升趋势可能正在到来。下面我列出了目前势头正旺的 9 个 Web…

PIPE接口基本描述

信号名方向描述TxDataIN并行数据输入总线TxDataValidIN1&#xff1a;表示cycle数据有效&#xff1b;0&#xff1a;表示cycle数据无效RxDataOUT并行数据输出总线RxValidOUT1&#xff1a;表示符号锁及当RxDataValid有效时&#xff0c;数据有效 PHY MODEINPHY STATUSOUT SerDesA…

java swing电子商务系统

一、项目简介 本项目是一套基于java swing的电子商务系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;ec…

opencv案例实战——银行卡模式匹配识别

系列文章目录 1.图像读取及其通道与灰度 2.图像填充与图像融合 3.图像滤波 4.图像阈值 5.腐蚀与膨胀 6.图像梯度 7.边缘检测 8.轮廓与轮廓特征 银行卡模式匹配识别系列文章目录前言案例介绍划分模板1.思路2.获取边缘3.获取外接矩形图像预处理切割礼帽操作分割数字块sobel算子膨…

LeetCode题解 二叉树(十):654 最大二叉树;617合并二叉树

654 最大二叉树 medium 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。左子树是通过数组中最大值左边部分构造出的最大二叉树。右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给定的数组构…