单例模式中懒汉模式的非线程安全问题的解决方法

单例模式中懒汉模式的非线程安全问题的解决方法

   单例模式中有饿汉模式和懒汉模式两种。饿汉模式也叫立即加载 ,即在get之前就已经创建实例instance;
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
哈希值相同,是单例模式的实现。但这种加了synchronized关键字后,多线程进行操作时只能等一个线程处理完,下一个线程才能进入同步方法中,效率低下。

二、同步代码块(效率低)

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;
	}
}

还有序列化和反序列化的单例模式来实现,或者使用枚举类来实现,在此就不一一介绍了,有兴趣可以百度下。

























  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
懒汉单例模式在第一次调用getInstance()方法时才会创建实例,如果多个线程同时调用getInstance()方法,就会创建多个实例,导致线程安全问题。为了解决这个问题,可以采用以下几种方式: 1. synchronized关键字同步getInstance()方法,确保多个线程同时访问时只有一个线程可以执行getInstance()方法,其他线程需要等待。但是这种方式会降低程序的性能,因为每个线程都需要获取锁。 2. 双重校验锁(double checked locking):在synchronized关键字上面加一层判断,当实例已经被创建时,直接返回实例,避免了不必要的锁操作。这种方式可以提高程序的性能,但是需要注意内存可见性问题。 ``` public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 3. 静态内部类:使用静态内部类创建单例对象,因为静态内部类只会被加载一次,所以不会有线程安全问题。当getInstance()方法第一次被调用时,会加载内部类并创建单例对象。 ``` public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } ``` 4. 枚举:使用枚举创建单例对象,枚举类型是线程安全的,并且只会被实例化一次。因此,枚举可以作为实现单例模式的一种方式。 ``` public enum Singleton { INSTANCE; public void doSomething() {...} } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值