设计模式 —— 单例模式

为什么要有单例的类呢?
有些对象的创建消耗时间和内存是非常大的,恰恰好这些对象在我们的应用中只需要使用 1 个,如果不能得到控制,会造成资源的浪费。
说明:就想我们的办公室、家里那些很贵的电器,比如电冰箱、空调、打印机、热水器这一类电器,一般情况下,在一个小范围内我们只用使用 1 个。比如我们办公室吧, 1 台打印机就够我们几个人用了,没有必要买 2 台打印机。类似地,Java 中有这样的一些对象,例如线程池、数据库连接池,一个应用程序中,我们只需要有 1 个这样的大对象。

很多朋友们学习设计模式,最先是从单例设计模式开始的,很容易地,我们知道,单例设计模式有两种写法:懒汉式与饿汉式,分别如下。



#饿汉式(线程安全)

  • 类加载的时候就创建对象

/**
 * 饿汉式单例类.在类初始化时,已经自行实例化
 * 是否 Lazy 初始化:否
 * 是否多线程安全:是
 * 实现难度:易
 * 描述:这种方式比较常用,但容易产生垃圾对象。
 * 优点:没有加锁,执行效率会提高。
 * 缺点:类加载时就初始化,浪费内存。
 * 它基于 classloder 机制避免了多线程的同步问题,
 * 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,
 * 在单例模式中大多数都是调用 getInstance 方法,
 * 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,
 * 这时候初始化 instance 显然没有达到 lazy loading 的效果。
 */
package singleton;

public class Singleton {

	// 构造器私有化,不能在类的外部随意创建对象
	private Singleton(){
	}
	
	 // 把"唯一的"对象保存在单例类的属性里
	private static final Singleton instance = new Singleton()	
	
	// 提供给外部获取该类的实例,唯一方式
	public static Singleton geInstance(){
		return instance;
	}
}
  • 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。



#懒汉式

  • 在第一次调用的时候才实例化自己

/** 
 * 懒汉式单例类.在第一次调用的时候才实例化自己
 * 是否 Lazy 初始化:是
 * 是否多线程安全:否
 * 实现难度:易
 * 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
 * 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*/   
package singleton;

	// 构造器私有化,不能在类的外部随意创建对象
	private Singleton(){
	};
	private static Singleton instance = null;	
	
	// 提供给外部获取该类的实例,唯一方式
	public static Singleton getInstance(){
		if(instance==null){
			instance = new Singleton();
		}
		return instance;
	}
}
  • 懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例。
  • 可以通过同步,对getInstance这个方法改造,
  • 保证了懒汉式单例的线程安全
  • (单线程测试是安全的,获取的都是同一实例;在多线程先获取到的对象不一定是同一个实例)


解决单例设计模式中懒汉式线程安全问题

解决方案:

package test;
/**
 * 懒汉式单例类.解决线程安全问题
 * 是否 Lazy 初始化:是
 * 是否多线程安全:是
 * 实现难度:易
 * 描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
 * 优点:第一次调用才初始化,避免内存浪费。
 * 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
 * getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
*/
public class Singleton {
	private Singleton(){	
	}
	private static Singleton instance = null;
	
	public static synchronized Singleton getInSingleton(){
		if(instance==null){
			instance = new Singleton();
		}
		return instance;
	}
}

分析:
这样使用同步锁机制,在多线程并发时,每个线程每次获取实例调用getInSingleton()方法时,都要判断下锁,效率肯定会比较低。


为了提高效率,这里加入了双重判断的方法,解决了效率的问题

代码如下;

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

用这种方式解决了懒汉式的线程安全问题,也提高了效率(线程安全 并且效率高 )


参考源于:
https://www.cnblogs.com/V1haoge/p/6510196.html
https://blog.csdn.net/lw_power/article/details/53261388
https://blog.csdn.net/youcanping2008/article/details/8579409
https://blog.csdn.net/qq_35098526/article/details/79893628

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值