以下例子参考
http://developer.51cto.com/art/201104/256239.htm
网上闲逛时发现一篇博文,讲的是单例同步锁时失败的可能,提到的错误自己基本都忽略了,下面以其中的例子说一下自己的理解。
单例模式是比较简单直接的:
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,根据博文的解释:
同步语句块内的操作必须在语句块结束之前完成,但是代码中同步块之外的操作有可能被编译器放到块内执行(只是存在这种可能性)。这一点细节确实不容易考虑到,需要了解一些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语义,简单点说就是保证了对象初始化以及赋引用的有序性,在本例来说就是先初始化后赋引用。这样两层机制保证了单例的唯一性和准确性。
分享到:
相关推荐
Redis同步锁AOP实现是一种在分布式系统中控制资源访问的有效机制。Redis,作为一个高性能的键值数据存储系统,常被用作分布式锁的底层支持。本文将深入探讨如何结合注解和面向切面编程(AOP)来实现Redis锁。 首先...
在这个“node+js页面乐观锁帧同步示例源码”中,我们可以看到一个结合Node.js和JavaScript实现的乐观锁应用。Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它让开发者可以使用JavaScript在服务器端编写代码...
在iOS开发中,多线程同步是保证代码...4. 对象的生命周期管理非常重要,避免在同步块内释放对象,否则可能导致同步失败。 了解这些知识点后,开发者可以根据实际需求选择合适的同步策略,确保多线程环境中的数据安全。
### 同步机制中的读写锁 (Read-Write Lock) #### 概述 在多线程编程中,读写锁是一种特殊的同步机制,用于解决读者写者问题。它允许多个线程同时读取共享资源,但只允许一个线程写入资源。这种机制通过将读操作和...
线程同步是多线程编程中的重要概念,旨在解决多个线程并发访问共享资源时可能出现的数据不一致性问题。当多个线程并发执行时,如果没有适当的同步机制,它们可能会同时访问和修改同一数据,导致结果不可预测。线程...
"新旧锁"可能意味着这个版本优化了旧版的授权系统,提升了用户体验,比如提高了解锁速度、增加了多设备间的同步功能,或者对旧版本的硬件锁提供了更好的兼容性。 从描述中的“深思最新版”可以推断,深思团队可能是...
6. `spin_trylock(lock)`:尝试立即获取自旋锁,成功则返回true,失败则返回false。 7. `spin_lock(lock)`:获取自旋锁,如果无法立即获取,则自旋等待。 8. `spin_lock_irqsave(lock, flags)`:获取自旋锁并保存...
重量级锁是阻塞锁的一种,在获取锁失败后,线程会被阻塞挂起,直到锁被释放,其他线程才有机会获得锁。重量级锁的实现依赖于操作系统的互斥量(mutex),使用内核资源,因此开销较大。 锁的优化还包括锁粗化和锁...
- 同步阻塞:尝试获取synchronized同步锁失败,线程进入等待锁的状态。 - 其他阻塞:调用sleep()、join()或进行I/O操作时,线程暂时释放CPU资源。 5. 死亡状态(Dead):线程的run()方法执行完毕或因异常退出,...
2. **非递归性**:如果一个线程已经持有了读锁,尝试再次获取读锁(或者已经持有写锁尝试获取写锁或读锁),将会导致操作失败。这是因为不支持重入,所以线程不能在已经持有锁的状态下再次请求同一类型的锁。 3. **...
锁对象是一种逻辑意义上的锁,有可能锁定的表条目在 DATABASE 上根本就不存在。 在创建锁对象时,需要在 LOCK PARAMETERS 里填写要根据哪些字段来锁定表条目。模式 E 是当更改数据的时候设置为此模式,模式 S 是...
如果失败,说明已有其他线程持有锁,这时轻量级锁升级为重量级锁。 3. **重量级锁**:重量级锁是通过操作系统互斥量进行实现的,锁标志位为10,表示锁已被锁定,此时Mark Word存储的是指向monitorentry对象的指针。...
第二条规则是`account required pam_tally.so`,它是用来同步用户登录尝试计数器的。 在PAM模块的配置中,还可以设置其他参数,如`no_lock_time`,其作用是设置在用户锁定后不需要任何解锁时间间隔即可再次尝试登录...
- **异常处理**: 需要考虑异常情况,比如获取锁失败、释放锁失败等,确保程序的健壮性和稳定性。 #### 五、总结 本文通过分析分布式锁的需求背景以及其实现原理,介绍了如何使用Redis实现简单的分布式锁。通过这种...
- 自旋锁是另一种优化策略,线程在获取锁失败时不会立即阻塞,而是循环检测锁是否可用,一旦可用立即获取。在高并发、锁竞争不激烈的情况下,自旋锁能提高效率。 理解这些锁的原理和使用场景是优化并发程序的关键...
- 设备管理:管理多个门锁设备,支持多锁同步控制。 3. **门锁接口的常见技术**: - **串行通信**:如RS-232、UART、USB等,通常用于设备间的短距离、低速通信。 - **网络通信**:如果门锁支持无线连接,可能...
1. **F_SETLK(非阻塞锁)**:尝试设置一个锁,如果锁定失败(例如,因为其他进程已经持有锁),则立即返回错误。 2. **F_SETLKW(阻塞锁)**:与F_SETLK类似,但不同之处在于,如果无法立即设置锁,它将使调用进程...
4. 锁与并发控制:在多线程环境下,数据同步需要考虑并发问题,可能需要使用数据库级别的锁或者乐观锁策略,防止数据冲突。 5. 错误处理与回滚:在数据同步过程中,可能会遇到各种错误,如网络中断、数据不一致等,...