这次和大家分享一下设计模式中的单例模式。
说到单例模式,我相信大家都了解,简单说就是某个类在整个程序中只有一个对象。那为什么使用单例模式呢?什么场景使用单例模式呢?
我个人认为,在程序设计中,经过分析,某个类有一个对象已经可以满足要求,如果此时再加上这个类会消耗许多资源(包括内存开销大,创建对象耗时等),这个类就推荐使用单例来实现了。比如:数据库连接及相关操作,图片加载,缓存,线程池等等。使用单例模式也可以更好的实现共享资源。
那如何实现单例模式呢?在这里我和大家分享五种方式。在这五种方式中,都是以一个简单的计算器例子来说明,计算加减乘除的类对象明显在程序中只要有一个就可以了。在这里只是为了介绍这些方式实现。
1. 饿汉模式
/**
* 饿汉模式实现单例模式
* 好处:简单
* 缺点:
* 当类加载的时候就创建了对象而不管对象时候在使用
* 造成资源的浪费
* 当内存不足的时候,回收了静态对象,此时访问也会带来bug
* @author wangpeiyu
*
*/
public class Hungry {
private static Hungry instance= new Hungry();
private Hungry(){
}
public Hungry getInstance(){
return instance;
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
2. 懒汉模式
/**
* 懒汉实现单例模式
* 好处:解决了线程安全,也做到了要使用的时候才创建对象
* 缺点:每次要获取该对象的时候,都要进行线程同步,即使在instance已经赋值了之后
* 还要进行线程同步,这就造成了不必要的时间浪费和资源浪费
* @author wangpeiyu
*
*/
public class Lazy {
private static Lazy instance=null;
private Lazy(){}
public synchronized static Lazy getInstance(){
if(instance==null){
instance = new Lazy();
}
return instance;
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
3. 静态内部类方式
/**
* 静态内部类实现单例模式
* 好处:线程安全、也达到了延迟加载、当加载单例类的时候并没有创建对象。
* 缺点:内存不足,回收静态对象时,在访问会出现bug
* 无法解决发序列化导致的对象重建
* @author wangpeiyu
*
*/
public class StaticInClass {
private StaticInClass(){
}
public static StaticInClass getInstance()
{
return SingletonHolder.instance;
}
/**
* 静态内部类
* 主要的单例类的holder
* @author wangpeiyu
*
*/
private static final class SingletonHolder{
private static final StaticInClass instance = new StaticInClass();
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
4. 双重检查加锁方式
/**
* 双重检查加锁模式实现单例模式
* 好处:延迟了加载、线程安全,而且也避免了在instance已经赋值的情况下,多线程还要
* 同步造成的资源浪费和无意义的等待
* 缺点:当反序列化时无法避免会重新创建对象的问题
* @author wangpeiyu
*
*/
public class DCL {
private static DCL instance = null;
private DCL(){}
public static DCL getInstance(){
if(instance==null){
synchronized (DCL.class) {
if(instance==null)
{
instance = new DCL();
}
}
}
return instance;
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
5. 枚举方法
/**
* 使用枚举实现单例
* 好处:
* 1.完全确保整个程序只有一个实例
* 2.线程安全,因为枚举一旦定义结束,编译器就不允许我们再使用其构造器来创建任何实例了
* 3.构造函数是private的,即使定义为public,一样为private
* 4.*序列化,这样即使在反序列化时也不会重新创建
* @author wangpeiyu
*
*/
public enum EnumCaculate {
instance;
private EnumCaculate(){
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
在这五种实现中,我个人比较推荐枚举实现和双重检查加锁实现。原因在每个类的代码中体现了。