同步锁的失败可能

探讨单例模式的多种实现及性能优化
本文深入分析单例模式的多种实现方式,包括简单实例、同步锁优化、原子性操作等,并讨论了不同实现方式在多线程环境下的性能优化策略。
以下例子参考[url]http://developer.51cto.com/art/201104/256239.htm[/url]

网上闲逛时发现一篇博文,讲的是单例同步锁时失败的可能,提到的错误自己基本都忽略了,下面以其中的例子说一下自己的理解。

单例模式是比较简单直接的:
public class Singleton {  

private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {

if(instance == null) {
instance = new Singleton();
}

return instance;
}
}

这也是在初学单例模式时会提到的一个采用延迟加载实现的例子,实际上这个例子并不能“保证”单例,在多线程高并发的情况下,如果瞬间同时初始访问getInstance方法,返回的可能就是不同的实例。于是就有了下面的简单的同步机制:
public class Singleton {  

private static Singleton instance = null;

private Singleton(){}

public synchronized static Singleton getInstance() {

if(instance == null) {
instance = new Singleton();
}

return instance;
}
}

这种方式保证了线程安全,但是我们只需要在生成实例的过程中保证其同步即可,不需要对访问也进行加锁。很明显,简单的在类实例范围内对其加锁,性能是其瓶颈。为了避免这种情况,便会有下面的想法:
public class Singleton {  

private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {

if(instance == null) {

synchronized(Singleton.class) {

if(instance == null) {

instance = new Singleton();

}
}
}

return instance;
}
}

其实这样便可以保证单例了,但是却保证不了单例的准确性。也就是说不同的线程可以拿到同一个实例,但是在某些情况下,会取到实例的错误状态。在本例中,初始化Singleton对象和将对象地址赋给instance的顺序是不确定的。也即可能有以下两种情况:
1、在初始化之前将对象引用赋给instance。这时,instance有了实际的引用,但对象还没有初始化。其他线程此时如果获取到instance,可能就是只有引用而还没有初始化的实例。
2、在初始化之后将对象引用赋给instance。这是正确的状态,保证了单例也保证了正确性。

这种不确定性不能“保证”单例的正确性。再看下面的改进方案:
public class Singleton {  

private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {

if(instance == null) {

Singleton temp;

synchronized(Singleton.class) {

temp = instance;

if(temp == null) {

synchronized(Singleton.class) {

temp = new Singleton();

}
instance = temp;
}
}
}

return instance;
}
}

这种方式制造了一个内存屏障,即使用了一个临时变量来保证“初始化操作和赋引用操作”的原子性。这种方式从代码方面看已经没有问题了,但是注意到在同步语句块之外的instance=temp,根据博文的解释:[color=red]同步语句块内的操作必须在语句块结束之前完成,但是代码中同步块之外的操作有可能被编译器放到块内执行(只是存在这种可能性)。[/color]这一点细节确实不容易考虑到,需要了解一些JVM的知识,这方面没什么研究,保留意见。
看最后一种方式:
public class Singleton {  

private static volatile Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {

if(instance == null) {

synchronized(Singleton.class) {

if(instance == null) {

instance = new Singleton();

}
}
}
return instance;
}
}

这里要说一下volatile关键字,这个除了在一定程度上确保同步之外,JDK1.5扩充了volatile语义,简单点说就是保证了对象初始化以及赋引用的有序性,在本例来说就是先初始化后赋引用。这样两层机制保证了单例的唯一性和准确性。
### C# 中同步锁的使用方法与示例代码 在多线程环境中,为了避免多个线程同时访问共享资源而导致的数据不一致问题,C# 提供了多种同步机制来保护临界区。其中最常用的方式之一就是通过 `lock` 关键字实现简单的锁机制。 #### `lock` 关键字的基本原理 `lock` 是一种简便易用的互斥锁工具,其核心作用在于确保同一时间只有一个线程能进入受保护的代码区域[^1]。这有助于防止因并发修改引发的竞争条件(Race Condition)。以下是 `lock` 的典型语法形式: ```csharp lock (objectToLock) { // 需要同步访问的代码块 } ``` 这里的 `objectToLock` 必须是一个引用类型对象,通常推荐将其声明为私有静态字段以避免不必要的性能开销和死锁风险[^3]。 #### 示例:使用 `lock` 保证线程安全 假设我们有一个类负责管理某种公共资源,并希望即使在高并发场景下也能保持数据一致性。下面展示了一个典型的例子——计数器累加操作: ```csharp public class CounterManager { private readonly object _syncRoot = new object(); private int _counterValue; public void Increment() { lock (_syncRoot) // 加锁部分 { _counterValue++; } } public int GetCounterValue() { lock (_syncRoot) // 确保读取时也加锁以防竞态条件 { return _counterValue; } } } ``` 在这个案例中,`_syncRoot` 被用来作为锁定的目标对象。每次调用 `Increment()` 方法或者查询当前值的时候都会先取得 `_syncRoot` 上的独占权限再继续后续动作,从而保障整个过程不会受到其他线程干扰。 #### 替代方案:手动控制监视器状态 (`Monitor`) 除了直接采用 `lock` 外,还可以借助更底层的 API —— `Monitor` 类完成相似功能。这种方式提供了更大的灵活性但也相应增加了复杂度[^2]: ```csharp private static readonly object MObjLock = new object(); public string GetData(int id) { Monitor.Enter(MObjLock); // 显式请求锁 try { // 执行可能抛异常的任务... Thread.Sleep(100); // 模拟耗时业务逻辑 return $"Data-{id}"; } catch(Exception ex) { Log4NetHelper.WriteError(ex.Message); throw; } finally { Monitor.Exit(MObjLock); // 不管成功失败都要释放锁 } } ``` 尽管如此,在大多数情况下仍然建议优先考虑 `lock` ,因为它不仅写起来更加直观而且还能自动处理一些常见错误情况比如忘记解锁等问题[^2]。 --- ### 总结 无论是选择 `lock` 还是 `Monitor` ,它们的根本目的都是为了协调不同线程间对于共同资源的竞争关系。合理设计同步策略不仅可以提升应用程序稳定性还能够改善用户体验。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值