条款21:static对象引起的多线程安全性

本文通过具体示例探讨了多线程环境下不同变量类型的访问安全性,包括局部变量、全局变量、静态变量等,并介绍了volatile关键字及InterlockedIncrement函数的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看到条款21:必须返回对象时,别妄想返回其reference时,看到一句话“就像所有用上static对象的设计一样,这一个也立刻造成我们对多线程安全性的疑虑。”虽然多线程有一点了解,但多线程安全性居然还没听过(囧)。

#include <windows.h>
#include <process.h>

long global1 = 0;
volatile long global2 = 0;

class MyClass
{
public:
	MyClass() : m(0)
	{
		++m;
	}

	int fun(int v)
	{
		return m + v; //-----------9
	}

	void set(int v)
	{
		m = v;   //-------------10
	}
	int m;
};

MyClass global_object; //-------------8

unsigned int __stdcall thread_fun1(void *param)
{
	static int static2 = 0;
	static MyClass static_object; //--------6
	int local1 = 0;

	++local1;     //-------1
	++static2;    //-------2
	++global1;    //-------3
	++global2;    //-------4
	InterlockedIncrement(&global1); //--------5

	local1 = global_object.fun(local1); //----------7

	global_object.set(local1); //---------------11

	return 0;
}


unsigned int __stdcall thread_fun2(void *param)
{
	++global1;    //-------3
	++global2;    //-------4
	InterlockedIncrement(&global1); //--------5

	global_object.set(1); //-----------11
	return 0;
}


int main()
{
	HANDLE thread1 = (HANDLE)_beginthreadex(0, 0, &thread_fun1, 0, 0, 0); //thread 1
	HANDLE thread2 = (HANDLE)_beginthreadex(0, 0, &thread_fun1, 0, 0, 0); //thread 2
	HANDLE thread3 = (HANDLE)_beginthreadex(0, 0, &thread_fun2, 0, 0, 0); //thread 3

	WaitForSingleObject(thread1, INFINITE);
	WaitForSingleObject(thread2, INFINITE);
	WaitForSingleObject(thread3, INFINITE);

	return 0;
}

1.局部变量局部使用是安全的
为什么?因为每个thread 都有自己的运行堆栈,而局部变量是生存在堆栈中,大家不干扰。
所以代码1
int local1;
++local1;
是安全的

2.全局原生变量多线程读写是不安全的
全局变量是在堆(heap)中
long global1 = 0;
++global2;
++这个操作其实分为两部,一个是读,另外一个是写(即不是原子操作)
mov         ecx,global
add         ecx,1
mov         global,ecx
所以代码3处是不安全的

3.函数静态变量多线程读写也是不安全的
道理同2
所以代码2处也是不安全的

4.volatile能保证全局整形变量是多线程安全的么
不能。
volatile仅仅是告诫compiler不要对这个变量作优化,每次都要从memory取数值,而不是从register
所以代码4也不是安全

5.InterlockedIncrement保证整型变量自增的原子性
所以代码5是安全的

6.function static object的初始化是多线程安全的么
不是。
著名的Meyer Singleton其实不是线程安全的
Object & getInstance()

     static Object o;
     return o;
}
可能会造成多次初始化对象
所以代码6处是不安全的

7.在32机器上,4字节整形一次assign是原子的
比如
i =10; //thread1
i=4; //thread2
不会导致i的值处于未知状态,要么是10要么是4

其它的大家自己去体会。

写好多线程安全的法宝就是封装,使数据有保护的被访问到
安全性:
局部变量>成员变量>全局变量

<think>嗯,用户这次询问的是Java中Unsafe类的功能和使用场景。从对话历史看,用户之前已经了解过CLH锁的实现机制,现在转向更底层的Unsafe类,说明ta对Java并发底层机制有系统性学习的兴趣。 用户提供的四个引用很有价值: 引用[1]强调了Unsafe的危险性和适用场景——性能关键代码,这点需要重点警示用户 引用[2]展示了获取实例的反射方法,这是实际使用的必经之路 引用[3]分类列出了五大功能,正好作为核心内容的骨架 引用[4]补充了其在JUC包中的基础作用,可关联之前讨论的CLH锁 用户可能存在的深层需求: 1. 想理解为什么AQS/原子类等并发工具依赖Unsafe 2. 好奇哪些场景必须使用Unsafe替代标准API 3. 需要明确的危险警示(特别是内存泄漏风险) 需要特别注意: - 必须强调“推荐直接使用”的立场 - 解释功能时要关联具体并发组件的实现(如之前讨论的CLH锁) - 内存屏障等概念需要简化说明 在结构上考虑: 1. 先定义Unsafe的定位(非公开API/底层后门) 2. 按功能分类说明(参考引用[3]的五大类) 3. 重点剖析CAS和内存操作(并发编程核心) 4. 必须包含严格的警告条款 5. 最后补充标准库替代方案(如VarHandle) 用户可能对以下细节感兴趣: - 为什么叫“Unsafe” - 对象字段偏移量的计算原理 - compareAndSwapInt的CPU指令对应关系 - park/unpark与普通锁的区别 需要避免: - 深入JNI实现细节(用户未要求) - 展示完整反射代码(引用[2]已足够) - 讨论已废弃方法(如allocateInstance) 在回答中考虑: - 将内存屏障与之前CLH锁的自旋等待关联 - 用CAS原理解释原子类的线程安全 - 指出Thread.resume()废弃后park/unpark的重要性</think>Java的`Unsafe`类是位于`sun.misc`包下的**底层操作工具**,它提供了直接操作内存、执行原子操作等能力,打破了Java的安全边界。其功能和使用场景如下: --- ### 一、核心功能 #### 1. **内存操作** - **堆外内存管理** 直接分配、释放堆外内存(绕过JVM垃圾回收): ```java long address = unsafe.allocateMemory(1024); // 分配1024字节 unsafe.setMemory(address, 1024, (byte) 0); // 初始化内存 unsafe.freeMemory(address); // 释放内存 ``` 适用场景:**高频IO操作**(如Netty的ByteBuf)、**大数据缓存**[^3][^4]。 #### 2. **CAS原子操作** - **硬件级原子指令** 通过`compareAndSwapXXX`方法实现无锁并发: ```java boolean success = unsafe.compareAndSwapInt(obj, offset, expect, update); ``` 原理:直接调用CPU的CAS指令(如x86的`CMPXCHG`),**避免线程阻塞**。 应用:`AtomicInteger`、`ConcurrentHashMap`等并发容器的底层实现[^1][^3]。 #### 3. **对象操作** - **绕过构造方法创建对象** ```java MyClass obj = (MyClass) unsafe.allocateInstance(MyClass.class); ``` 适用场景:**反序列化框架**(如Kryo)避免调用构造函数[^3]。 - **直接修改字段值** 通过字段偏移量(offset)读写对象字段: ```java long offset = unsafe.objectFieldOffset(MyClass.class.getDeclaredField("value")); unsafe.putInt(obj, offset, 100); // 直接修改字段值 ``` #### 4. **线程调度** - **线程挂起与恢复** ```java unsafe.park(false, 0); // 挂起当前线程 unsafe.unpark(thread); // 唤醒指定线程 ``` 应用:`LockSupport`的底层实现,用于AQS(如`ReentrantLock`)的线程阻塞[^3][^4]。 #### 5. **内存屏障** - **控制指令重排序** ```java unsafe.loadFence(); // 读屏障 unsafe.storeFence(); // 写屏障 unsafe.fullFence(); // 全屏障 ``` 作用:保障**多线程可见性**和**有序性**(如`volatile`关键字的底层实现)[^3][^4]。 --- ### 二、使用场景 1. **高性能并发工具** JUC包(如`AtomicXXX`、`AQS`)依赖Unsafe实现无锁算法。 2. **堆外内存管理** 需直接操作内存的库(如Netty、Ignite)。 3. **绕过语言限制** 框架中需动态创建对象或修改字段(如序列化库、依赖注入容器)。 4. **底层系统开发** JVM调试工具、性能监控代理等。 --- ### 三、严重警告 1. **破坏安全性** 可直接修改私有字段、跳过构造函数。 2. **内存泄漏风险** 手动管理内存易导致泄漏或非法访问(如`Unsafe#getInt`访问无效地址)。 3. **兼容性问题** `sun.misc.Unsafe`是内部API,同JDK版本可能变更或移除[^1][^2]。 4. **替代方案** Java 9+ 推荐使用`VarHandle`或`MethodHandles`实现类似功能,更安全可控。 > ⚠️ **总结**:仅限在**性能极端敏感**且**无替代方案**时由资深开发者使用,99%场景应优先使用标准库的`java.util.concurrent`或`java.nio`[^1][^4]。 --- ### 代码示例:获取Unsafe实例 ```java // 通过反射获取Unsafe实例(推荐生产环境使用) public static Unsafe getUnsafe() throws Exception { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); return (Unsafe) theUnsafe.get(null); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值