变量的线程安全分析

简介: 变量的线程安全分析

变量的线程安全

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

如果它们没有共享,则线程安全

如果它们被共享了,根据它们的状态是否能够改变,又分两种情况

  • 如果只有读操作,则线程安全
  • 如果有读写操作,则这段代码是临界区,需要考虑线程安全

局部变量是否线程安全?

局部变量是线程安全的

但局部变量引用的对象则未必

  • 如果该对象没有逃离方法的作用访问,它是线程安全的
  • 如果该对象逃离方法的作用范围,需要考虑线程安全

局部变量线程安全分析

1. public static void test1() {
2. int i = 10;
3.  i++;
4. }

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

局部变量的引用稍有不同

先看一个成员变量的例子

1. class ThreadUnsafe {
2.     ArrayList<String> list = new ArrayList<>();
3. 
4. public void method1(int loopNumber) {
5. for (int i = 0; i < loopNumber; i++) {
6.             { //临界区, 会产生竞态条件
7.                 method2();
8.                 method3();
9. // } 临界区
10.             }
11.         }
12.     }
13. 
14. private void method2() {
15.         list.add("1");
16.     }
17. 
18. private void method3() {
19.         list.remove(0);
20.     }
21. }

执行

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

1. Exception in thread "Thread0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
2.  at java.util.ArrayList.rangeCheck(ArrayList.java:659)
3.  at java.util.ArrayList.remove(ArrayList.java:498)
4.  at com.cc.db.ThreadUnsafe.method3(Gun.java:35)
5.  at com.cc.db.ThreadUnsafe.method1(Gun.java:24)
6.  at com.cc.db.ThreadUnsafe.lambda$main$0(Gun.java:16)
7.  at java.lang.Thread.run(Thread.java:750)

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

method3 与 method2 分析相同

将 list 修改为局部变量

1. class ThreadSafe {
2. public final void method1(int loopNumber) {
3.         ArrayList<String> list = new ArrayList<>();
4. for (int i = 0; i < loopNumber; i++) {
5.             method2(list);
6.             method3(list);
7.         }
8.     }
9. 
10. private void method2(ArrayList<String> list) {
11.         list.add("1");
12.     }
13. 
14. private void method3(ArrayList<String> list) {
15.         list.remove(0);
16.     }
17. }

那么就不会有上述问题了

分析:

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

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

情况1:有其它线程调用 method2 和 method3

情况2:在 情况1 的基础上,为 ThreadSafe 类添加子类,子类覆盖 method2 或 method3 方法,即

常见线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

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

1. Hashtable table = new Hashtable();
2. 
3. 
4. new Thread(()->{
5.  table.put("key", "value1");
6. }).start();
7. 
8. 
9. new Thread(()->{
10.  table.put("key", "value2");
11. }).start();
  • 它们的每个方法是原子的
  • 但注意它们多个方法的组合不是原子的

线程安全类方法的组合

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

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

即线程1判为空以后,并没有立刻put进去数据,而线程2这个时候也进行了判空,马上进行了put数据。线程1这时才put数据

不可变类线程安全性

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

Java中的String类是不可变类,这意味着一旦创建String对象,它的值就无法更改。因此,对于String对象的任何操作都不会影响原始对象,而是创建并返回一个新的String对象。

对于replace和substring等方法,实际上是返回了一个新的字符串对象,而不是在原始的字符串对象上进行修改,所以这样的操作不会引起线程安全问题。

具体来说,在Java中,String类的任何方法都是线程安全的,因为String类对象本身是不可变的,即String类的实例字段都声明为final类型,这些实例字段的值一旦被初始化后便无法更改,同时这些实例字段也没有提供任何可以修改它们的方法。因此,多个线程可以同时访问并共享同一个String对象,而不需要担心线程安全问题。

需要注意的是,如果在多线程环境下使用StringBuilder类或StringBuffer类,则需要采取相应的线程安全措施,因为它们是可变的字符串类,可能会引起线程安全问题。例如,可以使用synchronized关键字对StringBuilder类或StringBuffer类的共享代码块进行同步,或者使用ThreadLocal使得每个线程都拥有自己的StringBuilder或StringBuffer实例。


相关文章
|
3天前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
1月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
1月前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。
|
21天前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。
|
2月前
|
存储 监控 Java
|
2月前
|
安全 Java 开发者
Swing 的线程安全分析
【8月更文挑战第22天】
40 4
|
2月前
|
Java 数据库连接 数据库
当线程中发生异常时的情况分析
【8月更文挑战第22天】
75 4
|
2月前
|
安全 Java 程序员
线程安全与 Vector 类的分析
【8月更文挑战第22天】
25 4
|
2月前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
2月前
|
消息中间件 安全 Kafka
"深入实践Kafka多线程Consumer:案例分析、实现方式、优缺点及高效数据处理策略"
【8月更文挑战第10天】Apache Kafka是一款高性能的分布式流处理平台,以高吞吐量和可扩展性著称。为提升数据处理效率,常采用多线程消费Kafka数据。本文通过电商订单系统的案例,探讨了多线程Consumer的实现方法及其利弊,并提供示例代码。案例展示了如何通过并行处理加快订单数据的处理速度,确保数据正确性和顺序性的同时最大化资源利用。多线程Consumer有两种主要模式:每线程一个实例和单实例多worker线程。前者简单易行但资源消耗较大;后者虽能解耦消息获取与处理,却增加了系统复杂度。通过合理设计,多线程Consumer能够有效支持高并发数据处理需求。
99 4

热门文章

最新文章