线程不安全

众所周知,多线程访问同一公共资源会带来线程的不安全,本文探讨一下这个问题的若干细节。

关于线程安全的基本问题

有关线程安全常涉及两个概念:

竞态条件:当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。
临界区:导致竞态条件发生的代码区称作临界区(线程不安全的代码区)。

线程安全与竞态条件:线程安全的代码区不存在竞态条件,线程不安全的代码区(临界区)存在竞态条件。

为什么会出现线程安全问题?
道理很简单,多个线程同时访问(写操作)同一个公共资源必然带来问题,我们举2个例子:
例1:线程A,线程B同时拿到全局变量i(值为0)并存储在自己的本地栈中,线程A对i加1,线程B也对i加1,那么线程A,线程B提交后,i的结果为1,而不是我们期望的结果2。
例2:线程A,线程B同时拿到全局变量i(值为0)并存储在自己的本地栈中,线程A对i加1,线程B随后读取i,那么线程B读取的结果依然为0,而不是我们期望的结果1。这就是多线程并发导致的可见性问题。

如何解决线程不安全?
临界区进行同步,从而避免竞态条件。如:使用synchronized或JUC中的Lock对临界区加锁。给临界区加锁好比给公园的一个厕所加了一把锁,避免了人们共同进入厕所的问题,而必须是只有拿到钥匙的人才能进入(这个例子有点那个,但是我总会联想到这个例子)。

举例说明

既然多线程引发的安全问题是因为同时访问同一个公共资源导致的,相应的,如果多线程访问的不是公共资源也就不会发生线程安全问题。下面按资源是否公共举几个例子来说明问题。

公共资源:对象的成员变量

对象的成员变量:成员变量存储在共享堆上,如果两个线程同时更新同一个对象的同一个成员变量,那这个代码就不是线程安全的。
示例代码:

java;gutter:true; public class ThreadSafe_ { public static void main(String[] args) { Obj obj = new Obj(); MyRunnable task = new MyRunnable(obj); new Thread(task,"t1").start(); new Thread(task,"t2").start(); } } class Obj { StringBuilder noSafeBuilder = new StringBuilder();// 对象的成员变量:成员变量存储在共享堆上,如果两个线程同时更新同一个对象的同一个成员变量,那这个代码就不是线程安全的。</p> <pre><code>void add(String text){ noSafeBuilder.append(text); } </code></pre> <p>} class MyRunnable implements Runnable{ private Obj obj = null;</p> <pre><code>MyRunnable(Obj obj){ this.obj = obj; } @Override public void run() {// 临界区(线程不安全的代码区,因为多个线程可能同时修改成员变量noSafeBulider,所以会带来问题) this.obj.add(" "+Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName()+"=>"+this.obj.noSafeBuilder.toString()); } </code></pre> <p>}</p> <pre><code> 打印结果: t2=> t1 t2 t1=> t1 t2 当然,上面run方法因为线程不安全,无法保证线程的执行顺序,所以上面的代码运行多次可能带来多个结果:在这段代码中,我们期望的是,Obj对象的noSafeBuilder属性先追加1个线程名然后打印这个线程名,然后noSafeBuilder再追加第2个线程名并打印追加的2个线程名,但从打印结果看,第1个线程在执行完run方法后就已经打印了2个线程名——这就是线程不安全所带来的问题。 ![线程不安全](https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230526/797348-20210219200135010-1769034103.png) 从上图可以知道,线程不安全会带来多少问题。 解决办法: ;gutter:true;
class MyRunnable implements Runnable{
private Obj obj = null;

MyRunnable(Obj obj){
this.obj = obj;
}

private final Object lock = new Object();

@Override
public void run() {
synchronized (lock) {//临界区 加锁同步
this.obj.add(" "+Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+"=>"+this.obj.noSafeBuilder.toString());
}
}
}

打印结果:

t1=> t1

t2=> t1 t2

由于t2可能先执行,所以上面代码的打印结果也可能为:

t2=> t2

t1=> t2 t1

非公共资源:局部基本类型变量

局部变量存储在线程自己的栈中,也就是说,局部变量永远也不会被多个线程共享。如:

java;gutter:true; public class ThreadTest { public static void main(String[]args){ MyThread share = new MyThread(); for (int i=0;i</p> <pre><code> 无论多少个线程对run()方法中的基本类型a执行++a操作,只是更新当前线程栈的值,不会影响其他线程,也就是不共享数据。 ## 特殊资源:局部的对象引用 为什么这个是特殊示例呢?因为对象的局部引用和基础类型的局部变量不太一样,尽管引用本身没有被共享,但引用所指的对象并没有存储在线程的栈内。所有的对象都存在共享堆中。这就意味着访问对象的引用的代码可能是线程安全的,也有可能是线程不安全的——这主要取决于: **在某个方法中创建的对象是否会逃逸**(即该对象不会被其它方法获得,也不会被非局部变量引用到),如果不会,那么这个方法就是线程安全的,反之就是不安全的。 实际上,哪怕将这个对象作为参数传给其它方法,只要别的线程获取不到这个对象,那它仍是线程安全的。如: ;gutter:true;
public void method1(){
LocalObject localObject = new LocalObject();
localObject.callMethod();
method2(localObject);
}

public void method2(LocalObject localObject){
localObject.setValue("value");
}

Original: https://www.cnblogs.com/wql025/p/14391704.html
Author: Tom1997
Title: 线程不安全

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/542090/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 重复文件查找工具:Duplicate Cleaner V4.11绿色免费版

    Duplicate Cleaner是一款可以帮助你在你的计算机上找到并且快速查找出重复文件并标记出不同的颜色,让你轻松查阅处理。你可以立即搜索多个文件夹结构并且设置识别副本文件的标…

    Java 2023年5月30日
    0123
  • 【Java开发基础】计算两个毫秒之间相差多少天

    java;gutter:true; private long daysBeforeExpire() { return daysBetween(System.currentTimeM…

    Java 2023年5月29日
    0107
  • 设计模式概述(一)

    设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的…

    Java 2023年6月5日
    0119
  • Pydantic如何分离field和alias

    即假如需要返回的是: { "isStar": ture } 在python中一般不使用 isStar这种命名方式, 所以我们需要使用别名定义模型: class …

    Java 2023年6月7日
    080
  • 日程功能模块【从建模到代码实现】UML + JavaFX

    结合 uml 所学和 Javafx 从建模到实现一个子功能模块 —— 日程管理。新手上路,类图到代码实现的过程还是很曲折但所幸收获颇丰,记录一下学习心得。 日程功能模块 最后成果 …

    Java 2023年6月5日
    096
  • SpringBoot进阶教程(七十二)整合Apollo

    Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服…

    Java 2023年6月8日
    0104
  • ConcurrentHashMap中的get和put源码分析

    get分析 public V get(Object key) { // tab&#xFF1A;&#x6307;&#x5411;&#x6570;&am…

    Java 2023年6月16日
    080
  • Delphi 多线程的例子

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Contr…

    Java 2023年5月29日
    086
  • java Future CompletableFuture

    1.1 概述 Future没法直接对多个任务进行链式、组合等处理,而CompletableFuture是对Future的扩展和增强。CompletableFuture实现了Futu…

    Java 2023年5月29日
    0105
  • day04-3服务器推送新闻

    多用户即时通讯系统04 4.编码实现03 4.7功能实现-服务器推送消息功能实现 4.7.1思路分析 服务器推送新闻,本质其实就是群发消息 在服务器启动一个独立线程,专门负责推送新…

    Java 2023年6月15日
    0115
  • 泛型

    在JDK 1.5 之前,编译器允许我们向容器插入不同类据的数据。例 /* * 现在有一批 Apple * 需求:对这一批产品核对 id 再出仓 */ class Apple{ pr…

    Java 2023年6月5日
    081
  • 【LEETCODE】72、分割回文串 III 第1278题

    package y2019.Algorithm.dynamicprogramming.hard; /** * @Auther: xiaof * @Date: 2019/12/11 …

    Java 2023年6月5日
    080
  • 网络编程详解

    1.1 概述 计算机网络:计算机网络是指将 地理位置不同的具有独立功能的 多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及 网络通信协议的管理和协调下,…

    Java 2023年6月6日
    078
  • 根据表结构自动生成JavaBean,史上最强最专业的表结构转JavaBean的工具(第8版)

    第8版更新震撼发布,功能更加强大,速度过来围观,这次版本更新如下: 1、新增清除StringBuffer工具,是String字符串拼接工具的逆向工具。2、新增字符串格式化工具,提高…

    Java 2023年6月9日
    075
  • 阿里云 Docker 设置阿里云镜像加速

    1、登录阿里云 找到页面 &#x5BB9;&#x5668;&#x955C;&#x50CF;&#x670D;&#x52A1; 2、找到…

    Java 2023年6月5日
    071
  • 注解@PostConstruct分析

    1.注解@PostConstruct可以添加在类的方法上面,如果这个类被IOC容器托管,那么在对Bean进行初始化前的时候会调用被这个注解修饰的方法 被定义在哪里? 1.被定义在了…

    Java 2023年6月15日
    069
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球