变量的线程安全问题
成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
-
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
- 局部变量是线程安全的
- 但局部变量引用的对象则未必
-
- 如果该对象没有逃离方法的作用访问,它是线程安全的
- 如果该对象逃离方法的作用范围,需要考虑线程安全
局部变量线程安全分析
- 局部变量
//方法内部的局部变量不会共享、每个线程调用是会有各自的栈帧内存中创建多份。
public void test(){
int i= 10;
i++;
}
- 局部变量的引用
package cn.yds.juc.learning;
import java.util.ArrayList;
import java.util.List;
/**
* @author yds
* @Date 2022/9/28 10:37
* @Description 线程安全问题
* @Version 1.0.0
*/
public class 线程安全问题 {
static int loop1 = 2;
static int loop2 = 200;
public static void main(String[] args) {
ThreadUnsafe threadUnsafe = new ThreadUnsafe();
for (int i = 0; i < loop1; i++) {
new Thread(()->{
threadUnsafe.test(loop1);
},"t"+(i+1)).start();
}
}
}
class ThreadUnsafe {
//list是成员变量
List<String> list = new ArrayList();
public void test(int loop){
for (int i = 0; i < loop; i++) {
// 临界区,会产生竞态条件
add();
remove();
}
}
private void add() {
list.add("1");
}
private void remove() {
list.remove(0);
}
}
class ThreadSafe {
/**
* final 修饰的原因:不想让子类重写本方法(子类重新不可控),影响父类的功能。
*
*/
public final void test(int loop){
//list是局部 变量 没有暴露到外面 线程安全
List<String> list = new ArrayList();
for (int i = 0; i < loop; i++) {
add(list);
remove(list);
}
}
/**
* 为了保护线程安全,此处add和remove的访问修饰符 要特别注意。
* public 修饰修饰的方法可被子类重新,子类中的操作是不可控的。假设子类中重新开启线程操作list.也会有线程安全问题
*/
private void add(List<String> list) {
list.add("1");
}
private void remove(List<String> list) {
list.remove(0);
}
}
执行ThreadUnsafe
的Test
方法会出现报错,由于多个线程之间共享局部变量list造成的
Exception in thread "t1" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.add(ArrayList.java:463)
at cn.yds.juc.learning.ThreadUnsafe.add(线程安全问题.java:42)
at cn.yds.juc.learning.ThreadUnsafe.test(线程安全问题.java:36)
at cn.yds.juc.learning.线程安全问题.lambda$main$0(线程安全问题.java:19)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "t2" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.remove(ArrayList.java:505)
at cn.yds.juc.learning.ThreadUnsafe.remove(线程安全问题.java:46)
at cn.yds.juc.learning.ThreadUnsafe.test(线程安全问题.java:37)
at cn.yds.juc.learning.线程安全问题.lambda$main$0(线程安全问题.java:19)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "t95" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.remove(ArrayList.java:496)
at cn.yds.juc.learning.ThreadUnsafe.remove(线程安全问题.java:46)
at cn.yds.juc.learning.ThreadUnsafe.test(线程安全问题.java:37)
at cn.yds.juc.learning.线程安全问题.lambda$main$0(线程安全问题.java:19)
at java.lang.Thread.run(Thread.java:748)
而ThreadSafe
类将list
移动到了test()
方法内部(局部变量),以传参数的形式传入add()
和remove()
方法中。每个线程调用都会创建不同的实例,不会共享变量。
//为了线程安全,要控制变量或方法的访问权限。比如private(私有、本类中可用)、final修饰,防止外部对象或者子类操作变量。(开闭原则的闭)
常见的线程安全类
- String
- Integer
- StringBuffffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的类
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。如
Hashtable table = new Hashtable();
new Thread(()->{
table.put("key", "value1");
}).start();
new Thread(()->{
table.put("key", "value2");
}).start();
此处可查看HashTable的put源码:
public synchronized V put(K key, V value) {
//…………实现
return null;
}
方法被synchronize修饰保证(原子性)线程安全。
注意:他们里面的每个方法是原子性的 ,但是组合到一起并不能保证原子性。
不可变类线程安全性
String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的.
如String的subtring、replace等方法。看似对字符串做了操作,实则重新创建了新的对象。如:
public String substring(int beginIndex, int endIndex) {
//……
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);//此处new了新对象
}
本文来自博客园,作者:iyandongsheng,转载请注明原文链接:https://www.cnblogs.com/ieas/p/16738229.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!