单例模式中懒汉模式的非线程安全问题的解决方法 package singleton1; //饿汉模式(立即加载) public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton
单例模式中懒汉模式的非线程安全问题的解决方法
package singleton1;
//饿汉模式(立即加载)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
而懒汉模式也叫延迟加载,即在get时才会被创建实例。懒汉模式在多线程的环境中就会出现多实例的情况,与单例模式相背离。如下代码可证:
package singleton2;
//懒汉模式
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
try {
if(instance==null){
Thread.sleep(1000);//模拟延迟加载
instance=new Singleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}
创建线程:
package singleton2;
public class ThreadA extends Thread{
@Override
public void run(){
System.out.println(Singleton.getInstance().hashCode());//根据实例对象哈希值是否相同来判断对象是否相同
}
}
创建运行类:
package singleton2;
public class Run {
public static void main(String[] args) {
ThreadA t1=new ThreadA();
ThreadA t2=new ThreadA();
ThreadA t3=new ThreadA();
t1.start();
t2.start();
t3.start();
}
}
运行结果:
1321522194
2134502363
866891806
哈希值不同,所以对象不同,即不是单例模式。如何解决这个问题呢,下面提供了几种方法:
一、方法上声明synchronized关键字(效率低)
package singleton2;
//懒汉模式
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
//1 方法上声明synchronized关键字
synchronized public static Singleton getInstance(){
try {
if(instance==null){
Thread.sleep(1000);//模拟延迟加载
instance=new Singleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}
运行结果如下:
1876189785
1876189785
1876189785
二、同步代码块(效率低)
package singleton3;
//懒汉模式
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
try {
// 2 使用同步代码块,但效率低
synchronized(Singleton.class){
if(instance==null){
Thread.sleep(1000);//模拟延迟加载
instance=new Singleton();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}
运行结果:
1619327594
1619327594
1619327594
使用同步代码块方式也能解决上述问题,虽然不需要判断实例是否为null,但同样的也会带来效率低的毛病。
三、使用DCL(Double-Check Locking)双检查锁机制---(推荐)
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修饰instance?
原因:在于instance = new Singleton()的时候,在内存中实际上是分3步执行的:
1)分配对象的内存空间:memory = allocate();
2)初始化对象:ctorInstance(memory);
3)指向分配的地址:instance =memory
多线程在执行的时候,2 3可能发生重排序。即有可能线程A执行到第3步的时候,读取到instance不为null,就返回。实际上此时还未执行第二部即未初始化。
加上volatile就可以避免2 3步重排序来保证线程安全。
四、使用静态内部类来实现单例模式
package singleton6staticinnerclass;
public class Singleton {
//静态内部类进行初始化
private static class SingletonHandler{
private static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonHandler.instance;
}
}
五、static代码块来实现单例模式
package singleton7static;
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
//静态代码块
static{
instance=new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
还有序列化和反序列化的单例模式来实现,或者使用枚举类来实现,在此就不一一介绍了,有兴趣可以百度下。