1. 单例模式 java 双重锁用synchronized修饰之后还用volatile吗
没有volatile修饰的uniqueInstance
[java] view plain
public class Singleton {
private static Singleton uniqueInstance;
private Singleton(){
}
public static Singleton getInstance(){
if(uniqueInstance == null){ //#1
synchronized(Singleton.class){ //#2
if(uniqueInstance == null){ //#3
uniqueInstance = new Singleton(); //#4
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
} else {
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
}
}
}
return uniqueInstance;
}
}
这样可能会导致结果 Singleton被实例化两次 ,这样就不符合单例的特点
原因分析:
1. thread2进入#1, 这时子线程的uniqueInstance都是为空的,thread2让出CPU资源给thread3
2. thread3进入#1, 这时子线程的uniqueInstance都是为空的, thread3让出CPO资源给thread2
3. thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance。thread2执行完毕让出CPO资源给thread3
4. thread3接着#1跑下去,跑到#3的时候,由于#1里面拿到的uniqueInstance还是空(并没有及时从thread2里面拿到最新的),所以thread3仍然会执行#4,#5.1
5. 最后在thread2和thread3都实例化了uniqueInstance
例子2:用volatile修饰的uniqueInstance
这里就不贴重复的代码了,因为只是加多一个volatile来修饰成员变量:uniqueInstance,
这样可以创建出一个单例实例。
原因分析:
volatile(java5):可以保证多线程下的可见性;
读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的会跟主线程的一致。
写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。
1. thread2进入#1, 这时子线程的uniqueInstance都是为空的(java内存模型会从主线程拷贝一份uniqueInstance=null到子线程thread2),thread2让出CPU资源给thread3
2. thread3进入#1, 这时子线程的uniqueInstance都是为空的(java内存模型会从主线程拷贝一份uniqueInstance=null到子线程thread2), thread3让出CPO资源给thread2
3. thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance(由于是volatile修饰的变量,会马上同步到主线程的变量去)。thread2执行完毕让出CPO资源给thread3
4. thread3接着#1跑下去,跑到#3的时候,会又一次从主线程拷贝一份uniqueInstance!=null回来,所以thread3就直接跑到了#5.2
5. 最后在thread3不再会重复实例化uniqueInstance了
2. java单例双重检查锁为什么需要加volatile关键字
已经修改,的确应该加上volatile关键字。不加的情况下,假设两个线程,线程A正在执行instance = new Instance()的操作,而线程B开始执行if(instance==null)的判断,当不存在volatile的时候,因为 new Instance()是一个非原子操作,可能发生无序写入,构造函数可能在整个对象构造完成前执行完毕,线程B可能会看到一个不完整的instance对象,因为java的某些实现会在内存中开辟一片存储对象的区域后直接返回内存的引用,所以线程B判断不为null,而这时候实际上,instance的构造函数还没有执行,从而线程b得到不完整的对象。在 Instance 的构造函数执行之前,会在内存中开辟一片存储对象的区域后直接返回内存的引用,赋值给变量 instance,instance也就可能成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还会初始化的对象,这样会导致系统崩溃线程B可能会看到一个不完整的instance对象,因为java的某些实现,所以线程B判断不为null。从而得到不完整的对象。
3. Java单例模式双重检验锁的第一次是否为空判断是什么目的
第一次判断是否为空是位了保证是单例,只有初始是空的情况下才可以创建,synchronize里头的if判空是为了避免小概率事件发生,比如当对象还没创建时,有两个线程都通过了外部的判空,进入synchronize入口处,此时由于同步加锁,只有一个线程可以执行synchronize内部的代码(生成了单例对象),当它执行完释放了锁后,第二个线程就进入的synchronize内部的代码,如果此时不再判断一下的话,该对象就再次被创建了。
4. 如何在Java中使用双重检查锁实现单例
一个常见情景,单例类在多线程环境中违反契约。如果你要一个新手写出单例模式,可能会得到下面的代码:
private static Singleton _instance;
public static Singleton getInstance() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
然后,当你指出这段代码在超过一个线程并行被调用的时候会创建多个实例的问题时,他很可能会把整个getInstance()方法设为同步(synchronized),就像我们展示的第二段示例代码getInstanceTS()方法一样。尽管这样做到了线程安全,并且解决了多实例问题,但并不高效。在任何调用这个方法的时候,你都需要承受同步带来的性能开销,然而同步只在第一次调用的时候才被需要,也就是单例类实例创建的时候。这将促使我们使用双重检查锁模式(double checked locking pattern),一种只在临界区代码加锁的方法。程序员称其为双重检查锁,因为会有两次检查 _instance == null,一次不加锁,另一次在同步块上加锁。这就是使用Java双重检查锁的示例:
public static Singleton getInstanceDC() {
if (_instance == null) { // Single Checked
synchronized (Singleton.class) {
if (_instance == null) { // Double checked
_instance = new Singleton();
}
}
}
return _instance;
}
这个方法表面上看起来很完美,你只需要付出一次同步块的开销,但它依然有问题。除非你声明_instance变量时使用了volatile关键字。没有volatile修饰符,可能出现Java中的另一个线程看到个初始化了一半的_instance的情况,但使用了volatile变量后,就能保证先行发生关系(happens-before relationship)。对于volatile变量_instance,所有的写(write)都将先行发生于读(read),在Java 5之前不是这样,所以在这之前使用双重检查锁有问题。现在,有了先行发生的保障(happens-before guarantee),你可以安全地假设其会工作良好。另外,这不是创建线程安全的单例模式的最好方法,你可以使用枚举实现单例模式,这种方法在实例创建时提供了内置的线程安全。另一种方法是使用静态持有者模式(static holder pattern)。
/*
* A journey to write double checked locking of Singleton class in Java.
*/
class Singleton {
private volatile static Singleton _instance;
private Singleton() {
// preventing Singleton object instantiation from outside
}
/*
* 1st version: creates multiple instance if two thread access
* this method simultaneously
*/
public static Singleton getInstance() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
/*
* 2nd version : this definitely thread-safe and only
* creates one instance of Singleton on concurrent environment
* but unnecessarily expensive e to cost of synchronization
* at every call.
*/
public static synchronized Singleton getInstanceTS() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
/*
* 3rd version : An implementation of double checked locking of Singleton.
* Intention is to minimize cost of synchronization and improve performance,
* by only locking critical section of code, the code which creates instance of Singleton class.
* By the way this is still broken, if we don't make _instance volatile, as another thread can
* see a half initialized instance of Singleton.
*/
public static Singleton getInstanceDC() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null) {
_instance = new Singleton();
}
}
}
return _instance;
}
}
这就是本文的所有内容了。这是个用Java创建线程安全单例模式的有争议的方法,使用枚举实现单例类更简单有效。我并不建议你像这样实现单例模式,因为用Java有许多更好的方式。但是,这个问题有历史意义,也教授了并发是如何引入一些微妙错误的。正如之前所说,这是面试中非常重要的一点。
在去参加任何Java面试之前,要练习手写双重检查锁实现单例类。这将增强你发现Java程序员们所犯编码错误的洞察力。另外,在现在的测试驱动开发中,单例模式由于难以被模拟其行为而被视为反模式(anti pattern),所以如果你是测试驱动开发的开发者,最好避免使用单例模式。转载,仅供参考。
5. 在java中双重检查加锁为什么会失效
我个人认为既然是写的问题,可能对于程序来说同步代码块结束了,锁也就释放了,但是new这个过程是在计算机中真实存在的,因为是线程问题,如果在一个线程就不会出这种问题,总是建立好空间之后再运行下面,但是线程可能有极端情况这边还没建立好,那边已经开始读了。这样解释说得通。
6. JAVA考试试题:写一个带双锁机制的单例模式~ 160分!谢谢了,这个题我实在没有看懂,也做不来,忘大家
双重锁机制
namespaceSingleton
{
publicclassSingleton
{
//定义一个私有的静态全局变量来保存该类的唯一实例
;
//定义一个只读静态对象
//且这个对象是在程序运行时创建的
=newobject();
///
///构造函数必须是私有的
///这样在外部便无法使用new来创建该类的实例
///
privateSingleton()
{}
///
///定义一个全局访问点
///设置为静态方法
///则在类的外部便无需实例化就可以调用该方法
///
///
()
{
//这里可以保证只实例化一次
//即在第一次调用时实例化
//以后调用便不会再实例化
//第一重singleton==null
if(singleton==null)
{
lock(syncObject)
{
//第二重singleton==null
if(singleton==null)
{
singleton=newSingleton();
}
}
}
returnsingleton;
}
}
}
7. java中的lock锁,多次获得,只unlock一次,会有什么效果
摘要 Lock锁介绍:
8. 如何在Java中使用双重检查锁实现单例
public class SingleDemo {
private static SingleDemo s = null;
private SingleDemo(){}
public static SingleDemo getInstance(){
/*如果第一个线程获取到了单例的实例对象,
* 后面的线程再获取实例的时候不需要进入同步代码块中了*/
if(s == null){
//同步代码块用的锁是单例的字节码文件对象,且只能用这个锁
synchronized(SingleDemo.class){
if(s == null){
s = new SingleDemo();
}
}
}
return s;
}
}
用这种方式解决了懒汉式的线程安全问题,也提高了效率,但是在实际开发中还是用饿汉式的比较多,毕竟这个代码比较多,比较繁琐。