多线程带来的的风险-线程安全

v2-7bdded212ba4d459fb4fe10bcaa0021d_b

多线程带来的的风险-线程安全

~~ 多线程编程中,最难的地方,也是一个最重要的地方,还是一个最容易出错的地方,更是一个面试中特别爱考的地方.❤️❤️❤️

线程安全的概念

万恶之源,罪魁祸首是多线程的抢占式执行,带来的随机性.~~😕😕😕
如果没有多线程,此时程序代码执行顺序就是固定的,代码顺序固定,程序的结果就是固定的.
如果有了多线程,此时在抢占式执行下,代码执行的顺序,会出现更多的变数!!!
代码执行顺序的可能性就从一种情况变成无数种情况!!!
所以就需要保证这无数种线程调度顺序的情况下,执行的结果都是正确的!!!只要是有一种情况下,代码结果不正确,就视为线程不安全!!!

问题来了:能否消除这样的随机性了🤔🤔🤔?
调度的源头来自于操作系统的内核实现.
1.作为程序猿的我们改不了.😂😂😂
2.即使改了自己的操作系统,也无法推广开来,因为全世界大多数操作系统都是这样的,已成定局!😕😕😕

观察线程不安全(代码)😍😍😍

class Counter{
    public int count = 0;
    public void add(){
        count++;
    }
}
public class ThreadDemo13 {
    public static void main(String[] args) {
        Counter counter = new Counter();

        // 创建两个线程, 两个线程 counter 来调用 5W 次的add方法
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5_0000; i++) {
                counter.add();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5_0000; i++) {
                counter.add();
            }
        });
        // 启动线程
        t1.start();
        t2.start();

        // 等待两个线程结束
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // 打印最终的 count 值 预期结果: count = 10W
        System.out.println("count = "+ counter.count);
    }
}

运行结果:

image-20230923031309538
我们的需求是两个线程各自自增 5w次,一共自增 10w次,
预期结果是 10w,实际结果不是 10w而且每次都不一样.
程序出现了bug(程序不符合需求,就是bug).
注:这个就是典型的线程安全问题!!!😥😥😥

线程不安全的原因

~~ 为什么程序就出现了这个情况🤔🤔🤔?

线程与指令之间的关系:

一个线程要执行,就需要先编译成很多的CPU指令,写的任何一个代码都是要编译成很多的CPU指令的!!!
个人理解:一个线程是来完成一个任务,要做一些工作,而这个工作是可以分解成一个一个的小步骤的,每个小步骤就是一个指令.
由于线程的抢占式执行,导致当前执行到任意一个指令的时候,线程都有可能被调度走,然后CPU让别的线程来执行.

寄存器,CPU里重要的组成部分,寄存器也能存数据,空间更小,访问速度更快,CPU进行的运算都是针对寄存器(准确的说,是通用寄存器,如EAX,EBX,ECX)中的数据进行的

count++;
++ 操作本质上要分成三步
1.先把内存中的值,读取到CPU的寄存器中 ~~load
2.把CPU寄存器里的数值进行 +1运算 ~~add
3.把得到的结果写回到内存中 ~~ save
注:load,add,save就是CPU上执行的三个指令(被视为机器语言).

两个线程并发的执行count++,此时就相当于两组load,add,save进行执行,此时不同的线程调度顺序就可能会产生一些结果上的差异.

作图来理解多线程的调度:❤️❤️❤️

image-20230923125739880

分析执行过程:

image-20230923133152779

思考一下🤔🤔🤔:

出现bug 之后,得到的结果一定是 <= 10w, 结果是一定 >= 5w 嘛?
极端情况下,所有的执行都是交错执行,是否就是 5w 呢??
实际上,结果是可以小于 5w ,只是概率更低了!!
image-20230923133717932

根结底线程安全问题全是因为==线程的无序调度(罪魁祸首,万恶之源)==导致了执行顺序不确定结果就变化了!!!

总结(线程不安全的原因)😊😊😊

  1. [根本原因] 抢占式执行,随机调度 ~~ 对此,我们无能为力.

  2. 代码结构:

    • 多个线程 同时 修改 同一个变量 ~~ 不安全!!! 😥😥😥

    • 一个线程,修改一个变量,安全.

    • 多个线程读取同一个变量,安全.

    • 多个线程修改多个不同的变量,安全.

    • 注1: 因此可以通过代码结构来规避这个线程不安全问题,
      但是因为需求问题,代码结构无法进行调整(这种方法使用频率并不高).

    • 注2: 修改 => 不可变对象是无法修改的,天然就是线程安全的!!!

  3. 原子性. 如果修改操作是原子的,出现问题概率小;如果是非原子的,出现问题概率极高(线程不安全问题,其实本质上是事务的脏读问题,之前博主写的博客解释过相关概念,在此不做解释啦!ヾ(❀^ω^)ノ゙

    • 原子: 不可拆分的基本单位.

      上述 count ++ 操作就不是原子,里面可以拆分成三个操作,load,add,save.某个操作,对应单个CPU指令,就是原子的,如果这个操作对应多个CPU指令,大概率就不是原子的.比如直接使用 = 赋值,就是一个原子的操作

  4. 内存可见性,引起的线程不安全

    • 一个线程读,一个线程改,可能出现读的结果和预期不符合的问题.
  5. 指令重排序,引起的线程不安全

    • 本质上是编译器优化,优化出bug了.优化:编译器觉得程序猿写的代码太low了,对代码进行调整,在保持代码逻辑不变的情况下,调整代码的执行顺序,从而加快程序的执行效率.

线程不安全问题的解决

**如何解决线程不安全问题?**🤨🤨🤨🤨
最主要的手段就是从这个原子性下手,通过加锁不能把非原子的操作变成原子的.
即解决前面代码的线程不安全问题,就是通过加锁让count++变成原子的.
synchronized public void add(){ count++; }

加了 synchronized 之后,进入方法就会加锁,出了方法就会解锁.
如果两个线程同时尝试加锁,此时一个能获取锁成功,另一个只能阻塞等待(处于BLOCKED状态),一直阻塞到刚才的状态解锁(释放锁),当前线程才能加锁成功!!! => 操作系统的基本设定,系统里的锁“不可剥夺”特性,一旦一个线程获取到锁,除非它主动释放,否则无法强占.

加锁,说是保证原子性,不是让load,add,save三个操作一次完成,也不是就是让其它也想操作的线程阻塞等待了
image-20230923205141702

image-20230923203926850

虽然加锁之后,算得慢了,但是还是比单线程要快,加锁只是针对count++加锁了,除了count++之外,还有for循环的代码,for循环代码是可以并发执行的(线程t1和线程t2各自修改各自for循环的局部变量i,是没问题的),只是count++串行执行了.
一个任务中,一部分可以并发,一部分串行,仍然是比所有代码串行要快的.


加锁,是要明确执行对哪个对象加锁的.如果两个线程针对同一个对象加锁,会产生阻塞等待(锁竞争/锁冲突),如果两个线程针对不同对象加锁,不会阻塞等待(不会锁冲突/锁竞争).

synchronized的使用方法😊😊😊😊

  1. 修饰方法
    • 修饰普通方法 ~~ 把锁加到this对象上
      • 进入方法就加锁,离开方法就解锁
    • 修饰静态方法 ~~ 把锁加到类对象上
      • 通理也是这样,进入方法就加锁,离开方法就解锁
    • 但是这两种修饰方法,加锁的“对象”不同,修饰普通方法,锁对象就是this,修饰静态方法,锁对象就是类对象.
      image-20230924235103704
  2. 修饰代码块
    • 显示/手动指定锁对象
  3. 锁对象
    1. 明确锁对象,针对那个对象加锁
    2. 两个线程,针对同一个对象加锁,会发生锁冲突/锁竞争(产生阻塞等待)
      • 一个线程获取到锁(先到先得),另一个线程就得塞等待,等待到上一个线程解锁,它才能获取锁成功.
    3. 两个线程,针对不同的对象加锁,不会有任何锁冲突
      • 这两个线程都能获取到各自的锁,此时不会有阻塞等待了.
  • 两个线程,一个线程加锁,一个线程不加锁,这个时候就不存在锁竞争了.
    image-20230925000413172注: 这种写法和没加锁的运行结果一样.
  • 63
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 47
    评论
前 言 第1章 简介 1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 1.2.4 响应更灵敏的用户界面 1.3 线程带来风险 1.3.1 安全性问题 1.3.2 活跃性问题 1.3.3 性能问题 1.4 线程无处不在 第一部分 基础知识 第2章 线程安全性 2.1 什么是线程安全性 2.2 原子性 2.2.1 竞态条件 2.2.2 示例:延迟初始化中的竞态条件 2.2.3 复合操作 2.3 加锁机制 2.3.1 内置锁 2.3.2 重入 2.4 用锁来保护状态 2.5 活跃性与性能 第3章 对象的共享 3.1 可见性 3.1.1 失效数据 3.1.2 非原子的64位操作 3.1.3 加锁与可见性 3.1.4 Volatile变量 3.2 发布与逸出 3.3 线程封闭 3.3.1 Ad-hoc线程封闭 3.3.2 栈封闭 3.3.3 ThreadLocal类 3.4 不变性 3.4.1 Final域 3.4.2 示例:使用Volatile类型来发布不可变对象 3.5 安全发布 3.5.1 不正确的发布:正确的对象被破坏 3.5.2  不可变对象与初始化安全性 3.5.3 安全发布的常用模式 3.5.4 事实不可变对象 3.5.5 可变对象 3.5.6 安全地共享对象 第4章 对象的组合 4.1 设计线程安全的类 4.1.1 收集同步需求 4.1.2 依赖状态的操作 4.1.3 状态的所有权 4.2 实例封闭 4.2.1 Java监视器模式 4.2.2 示例:车辆追踪 4.3 线程安全性的委托 4.3.1 示例:基于委托的车辆追踪器 4.3.2 独立的状态变量 4.3.3 当委托失效时 4.3.4 发布底层的状态变量 4.3.5 示例:发布状态的车辆追踪器 4.4 在现有的线程安全类中添加功能 4.4.1 客户端加锁机制 4.4.2 组合 4.5 将同步策略文档化 第5章 基础构建模块 5.1 同步容器类 5.1.1 同步容器类的问题 5.1.2 迭代器与Concurrent-ModificationException 5.1.3 隐藏迭代器 5.2 并发容器 5.2.1 ConcurrentHashMap 5.2.2 额外的原子Map操作 5.2.3 CopyOnWriteArrayList 5.3 阻塞队列和生产者-消费者模式 5.3.1 示例:桌面搜索 5.3.2 串行线程封闭 5.3.3 双端队列与工作密取 5.4 阻塞方法与中断方法 5.5 同步工具类 5.5.1 闭锁 5.5.2 FutureTask 5.5.3 信号量 5.5.4 栅栏 5.6 构建高效且可伸缩的结果缓存 第二部分 结构化并发应用程序 第6章 任务执行 6.1 在线程中执行任务 6.1.1 串行地执行任务 6.1.2 显式地为任务创建线程 6.1.3 无限制创建线程的不足 6.2 Executor框架 6.2.1 示例:基于Executor的Web服务器 6.2.2 执行策略 6.2.3 线程池 6.2.4 Executor的生命周期 6.2.5 延迟任务与周期任务 6.3 找出可利用的并行性 6.3.1 示例:串行的页面渲染器 6.3.2 携带结果的任务Callable与Future 6.3.3 示例:使用Future实现页面渲染器 6.3.4 在异构任务并行化中存在的局限 6.3.5 CompletionService:Executor与BlockingQueue 6.3.6 示例:使用CompletionService实现页面渲染器 6.3.7 为任务设置时限 6.3.8 示例:旅行预定门户网站 第7章 取消与关闭 第8章 线程池的使用 第9章 图形用户界面应用程序 第三部分 活跃性、性能与测试 第10章 避免活跃性危险 第11章 性能与可伸缩性 第12章 并发程序的测试 第四部分 高级主题 第13章 显式锁 第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章 Java内存模型 附录A 并发性标注
中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第1页。中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第1页。www.csbit.cn 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第1页。 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第1页。 中安威士数据库防火墙系统(VS-FW) 产品概述 中安威士数据库防火墙系统,简称VS-FW。通过实时分析对数据库的访问流量,自动建立合法访问数据库的特征模型,发现和过滤对数据库的违规访问和攻击行为。主要功能包括:屏蔽直接访问数据库的通道、多因子认证、自动建模、攻击检测、访问控制、审计等。该产品具有高性能、大存储和报表丰富等优势。能为客户带来如下价值: 自动识别用户对敏感数据的访问行为模式,识别数据库的安全威胁,并定期更新攻击特征库 全面审核企业内部和外部人员对敏感数据的所有访问,提高数据安全管理能力 报警并阻止对数据库的非法访问和攻击,完善纵深防御体系,提升整体安全防护能力 避免核心数据资产被侵犯,保障业务安全运营 丰富的报表,帮助企业满足合规审计要求,快速通过评测 产品功能 屏蔽直接访问数据库的通道 数据库防火墙部署于数据库服务器和应用服务器之间,屏蔽直接访问数据库的通道,防止数据库隐通道对数据库的攻击,见下图。 多因子认证 基于IP地址、MAC地址、用户、应用程序、时间等因子对访问者进行身份认证,形成多因子认证,弥补单一口令认证方式安全性的不足。应用程序对数据库的访问,必须经过数据库防火墙和数据库自身两层身份认证。 攻击检测和保护 实时检测用户对数据库进行的SQL注入和缓冲区溢出攻击。并报警或者阻止攻击行为,同时详细的记录攻击操作发生的时间、来源IP、用户名、攻击代码等信息。 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第2页。中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第2页。特征基线—自动建立访问模型 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第2页。 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第2页。 系统将自动学习每一个应用的访问语句,进行模式提取和分类,自动生成行为特征模型,并可以对学习结果进行编辑。系统通过检查访问行为与基线的偏差来识别风险。 虚拟补丁 数据库系统是个复杂的系统,自身具有很多的漏洞,容易被攻击者利用从而导致数据泄漏或致使系统瘫痪。而由于需要保证业务连续性等多种原因,用户通常不会及时对数据库进行补丁安装。中安威士防火墙通过内置的多种策略防止已知漏洞被利用,并有效的降低数据库被零日攻击的风险安全审计 系统能够审计对数据库服务器的访问情况。包括用户名、程序名、IP地址、请求的数据库、连接建立的时间、连接断开的时间、通信量大小、风险等信息。并提供灵活的查询分析功能。 报表 提供丰富的报表模板,包括各种审计报表、安全趋势等。 特性优势 技术优势 全自主技术体系:形成高技术壁垒 高速分析技术:特殊数据包分析和转发技术,实现高效的网络通信内容过滤多线程技术和缓存技术,支持高并发连接 基于BigTable和MapReduce的存储:单机环境高效、海量存储 基于倒排索引的检索:高效、灵活日志检索、报表生成 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第3页。中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第3页。高性能 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第3页。 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第3页。 连续处理能力:业界最高的SQL处理能力 日志检索速度:业界最快的模糊日志检索速度 日志存储能力: 业界最高的单位磁盘存储能力 高可用性 基于硬件的Bypass功能,防止单点失败 支持双机热备功能,保证连续服务能力 支持自动日志备份 支持SNMP、Syslog日志外发 支持时间同步 高安全性 细颗粒度的访问控制 可以灵活的对每个应用程序的访问权限进行配置 支持黑名单、白名单规则 典型部署 中安威士数据库防火墙支持多种部署方式,适用于各种网络场景。 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第4页。中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第4页。案例1:数据防火墙阻止社保信息泄漏 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第4页。 中安威士数据库防火墙系统(VS-FW)全文共5页,当前为第4页。 需求背景 2015年5月,三十多个省市卫生和社保系统出现大量高危漏洞,上亿用户的社保信息因此被泄漏。社保系统包括居民身份证、社保、薪酬等敏感信息,一旦这些信息泄露,不仅是个人隐私全无,还会被犯罪分子利用来复制身份证、盗办信用卡、盗刷信用卡等进行刑事犯罪和经济犯罪。因此亟需相应的解
### 回答1: 在 Java 中实现高性能的单例模式有多种方法。其中一种常用的方法是使用“双重检查加锁”(double-checked locking)。 这种方法的基本思路是:首先检查实例是否已经创建,如果尚未创建,才进行同步。这样,只有第一次执行单例的创建代码时,才需要进行同步。这能够大大减少同步带来的性能损耗。 具体实现如下: ``` public class HighPerformanceSingleton { private static volatile HighPerformanceSingleton instance; private HighPerformanceSingleton() {} public static HighPerformanceSingleton getInstance() { if (instance == null) { synchronized (HighPerformanceSingleton.class) { if (instance == null) { instance = new HighPerformanceSingleton(); } } } return instance; } } ``` 在这段代码中,第一次检查(instance == null)是为了避免不必要的同步,第二次检查(instance == null)是为了在 null 的情况下创建实例。 需要注意的是,在 Java 5 之前的版本中,由于 JVM 具有乱序执行的特性,在单例的创建代码中存在指令重排序的风险,因此在这些版本中,双重检查加锁可能并不会真正实现单例,为了避免这个问题,可以使用 volatile 关键字来禁止指令重排序。 ### 回答2: 多线程下高性能的Java单例可以通过以下几个步骤实现: 1. 使用双重校验锁(Double-Checked Locking)来保证只有第一次调用getInstance()方法时才会进行同步处理,避免每次调用都进行同步。在双重校验锁中,需要在同步代码块之前和之后进行两次判空操作,以确保只有在实例未被创建的情况下才进行同步处理。 2. 使用volatile关键字修饰实例变量,以保证在多线程环境下的可见性。volatile关键字可以防止指令重排序,确保在实例化对象时的相关指令被正确执行。 3. 将实例化的过程放在静态内部类中实现,通过静态内部类的特性,在应用启动时进行延迟加载,避免对整个类进行同步处理。静态内部类在第一次被使用时才会被加载,从而实现了懒加载的效果。 4. 在getInstance()方法中进行对象的双重判空处理,以确保在并发情况下只会有一个线程成功获取到实例对象。可以使用同步块,在实例对象为空时进入同步块,并再次判断实例对象是否为空,如果为空则进行实例化。 实施上述步骤后,可以实现多线程下高性能的Java单例。这样做可以避免每次调用getInstance()方法都进行同步处理,提高了并发访问的性能。同时通过双重校验锁和静态内部类的方式,保证了线程安全性和懒加载的效果。 ### 回答3: 在多线程环境下,Java单例模式需要考虑线程安全性和性能。以下是一种实现高性能的多线程下的Java单例的方法: 1. 使用双重检查锁定: 使用双重检查锁定(double-checked locking)可以在保持线程安全的同时提高性能。具体实现如下: ```java public class Singleton { private static volatile Singleton instance; private Singleton() { // 私有构造方法 } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 在getInstance方法中,首先检查instance是否为null,如果为null则进行同步操作,再次检查instance是否为null,然后创建实例。使用关键字volatile可以保证变量在多线程环境下的可见性和有序性。 2. 使用静态内部类: 静态内部类在首次使用时进行加载,可以保证只有一个线程能够创建实例。具体实现如下: ```java public class Singleton { private Singleton() { // 私有构造方法 } private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } ``` 在静态内部类SingletonHolder中创建实例,getInstance方法直接返回该实例,由于内部类只会被加载一次,保证了线程安全性。 综上所述,通过双重检查锁定和静态内部类的方式可以实现高性能的多线程下的Java单例模式,保证了线程安全性的同时提高了性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 47
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值