3.3、变量的线程安全分析

2024-06-12 05:32
文章标签 分析 线程 安全 变量 3.3

本文主要是介绍3.3、变量的线程安全分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

变量的线程安全分析

成员变量和静态变量是否线程安全?

  • 如果它们没有共享,则线程安全
  • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
    • 如果只有读操作,则线程安全
    • 如果有读写操作,则这段代码是临界区,需要考虑线程安全

局部变量是否线程安全

  • 局部变量是线程安全的
  • 单局部变量引用的对象则未必
    • 如果该对象没有逃离方法的作用访问,它是线程安全的
    • 如果该对象逃离方法的作用范围,需要考虑线程安全

局部变量线程安全分析

public static void test1() {int i = 10; i++;
}

每个线程调用test1()方法时局部变量i,会在每个线程的栈帧内存中被创建多份,因此不存在共享

public static void test1();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATICCode:stack=1, locals=1, args_size=00: bipush 102: istore_03: iinc 0, 16: returnLineNumberTable:line 10: 0line 11: 3line 12: 6LocalVariableTable:Start Length Slot Name Signature3 4 0 i I

如图
在这里插入图片描述
局部变量的引用稍有不同
先看一个成员变量的例子

public class Test2 {public static void main(String[] args) {ThreadUnsafe threadUnsafe = new ThreadUnsafe();for (int i = 0; i < 2; i++) {new Thread(() -> {threadUnsafe.method1(200);}, "t" + i).start();}}
}class ThreadUnsafe {ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {for (int i = 0; i < loopNumber; i++) {// 临界区,会产生竞态条件method2();method3();}}private void method2() {list.add("1");}private void method3() {list.remove(0);}}

其中一种情况是,如果线程2还未add,线程1 remove就会报错:

Exception in thread "t1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0at java.util.ArrayList.rangeCheck(ArrayList.java:657)at java.util.ArrayList.remove(ArrayList.java:496)at sync.test.ThreadUnsafe.method3(Test2.java:39)at sync.test.ThreadUnsafe.method1(Test2.java:30)at sync.test.Test2.lambda$main$0(Test2.java:15)at java.lang.Thread.run(Thread.java:748)

具体原因是:

new Thread(() -> {list.add("1");        // 时间1. 会让内部 size ++list.remove(0); // 时间3. 再次 remove size-- 出现角标越界
}, "t1").start();new Thread(() -> {list.add("2");        // 时间1(并发发生). 会让内部 size ++,但由于size的操作非原子性,  size 本该是2,但结果可能出现1list.remove(0); // 时间2. 第一次 remove 能成功, 这时 size 已经是0
}, "t2").start();

分析:

  • 无论哪个线程中的method2引用的都是同一个对象中的list成员变量
  • method3与method2分析相同

在这里插入图片描述
将list修改为局部变量

public class Test2 {public static void main(String[] args) {ThreadUnsafe threadUnsafe = new ThreadUnsafe();for (int i = 0; i < 2; i++) {new Thread(() -> {threadUnsafe.method1(200);}, "t" + i).start();}}
}class ThreadUnsafe {//    ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {// 临界区,会产生竞态条件method2(list);method3(list);}}private void method2(ArrayList<String> list) {list.add("1");}private void method3(ArrayList<String> list) {list.remove(0);}}

就不会有上述的问题了
在这里插入图片描述
分析

  • list是局部变量,每个线程调用时会创建不同的实例,没有共享
  • 而method2的参数是从method1中传递过来的,与method1中引用同一个对象
  • method3同理

方法访问修饰符带来的思考,如果把method2和method3的方法修改为public会不会有线程安全问题?

  • 情况1:有其他线程调用method2和method3
  • 情况2:在情况1的基础上,为ThreadSafe类添加子类,子类覆盖method2和method3方法

即:


class ThreadUnsafe {//    ArrayList<String> list = new ArrayList<>();public void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {// 临界区,会产生竞态条件method2(list);method3(list);}}public void method2(ArrayList<String> list) {list.add("1");}public void method3(ArrayList<String> list) {list.remove(0);}}class ThreadUnsafeSub extends ThreadUnsafe {@Overridepublic void method3(ArrayList<String> list) {new Thread(() -> {list.remove(0);}).start();}
}

从这个例子可以看出private或final提供【安全】的意义所在,请体会开闭原则中的【闭】

常见的线程安全类

  • String
  • integer
  • StringBuffer
  • Random
  • Vector
  • hashtable
  • java.util.concurrent包下的类

这里说他们是线程安全的是指,多个线程调用他们同一个实例的某个方法时,是线程安全的,也可以理解为

 public static void main(String[] args) {Hashtable<String, String> hashtable = new Hashtable<>();new Thread(() -> {hashtable.put("1", "1");}).start();new Thread(() -> {hashtable.put("2", "2");}).start();}
  • 他们的每个方法是原子的
  • 注意他们多个方法的组合不是原子的

线程安全类方法的组合

分析下面代码是否线程安全?

Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {table.put("key", value);
}

在这里插入图片描述

不可变类线程安全性

String、Integer等都是不可变类,因为其内部的状态不可以改变,因此他们的方法都是线程安全的
String的replace,substring等方法【可以】改变值,那么这些方法是如何保证线程安全的呢?

public class Immutable {private int value = 0;public Immutable(int value) {this.value = value;}public int getValue() {return this.value;}public Immutable add(int v) {return new Immutable(this.value + v);}}

实例分析

暂略

这篇关于3.3、变量的线程安全分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1053322

相关文章

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Python从零打造高安全密码管理器

《Python从零打造高安全密码管理器》在数字化时代,每人平均需要管理近百个账号密码,本文将带大家深入剖析一个基于Python的高安全性密码管理器实现方案,感兴趣的小伙伴可以参考一下... 目录一、前言:为什么我们需要专属密码管理器二、系统架构设计2.1 安全加密体系2.2 密码强度策略三、核心功能实现详解

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO