设计模式之单例模式(Java)

设计模式之单例模式实现

单例模式是确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免多地同时修改状态。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

  Java 编写单例模式有七种写法
第一种 懒汉式单例

package com.xiaokai.constructor.singleton;

/**
 * Created by Administrator on 2016/8/4.
 */
public class Singleton1 {
    //私有静态实例(全局共享)
    private static Singleton1 instance;
    //私有构造器(外部不能实例化)
    private Singleton1() {
    }
    //获取单例
    public static Singleton1 getInstance() {
        //如果没有实例化,先实例化返回
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
}
单元测试类
package com.xiaokai.constructor.singleton;

import org.junit.Assert;
import org.junit.Test;


/**
 * Created by Administrator on 2016/8/4.
 */
public class Singleton1Test {
    @Test
    public void getInstance() throws Exception {
        Singleton1 singleton1 = Singleton1.getInstance();
        Singleton1 singleton11 = Singleton1.getInstance();
        Assert.assertSame(singleton1, singleton1);
    }

}
说明:此懒汉式单例在多线程不能正常工作(非线程安全),并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全。

1、在getInstance方法上加同步

//在获取单例方法上加锁
public static synchronized Singleton1 getInstance() {
         if (single == null) {  
             single = new Singleton1();
         }  
        return single;
}
这种写法虽然能够在多线程中很好的工作,但是,他的效率很低,因为单例已经存在的情况下是不需要同步的。

2、双重检查锁定

//加锁前先判单例是否已经存在,只有在不存的时候加锁实例化出来一个单例
public static Singleton1 getInstance() {
        if (singleton == null) {
        //注意是类级锁  
            synchronized (Singleton1.class) {  
               if (singleton == null) {  
                  singleton = new Singleton1(); 
               }  
            }  
        }  
        return singleton; 
    }
注意:双重检查锁定在JDK1.5之后,才能够正常达到单例效果。

3、使用静态内部类实现单例模式(推荐)

public class Singleton { 
    //定义私有静态内部类 
    private static class SingletonHolder { 
        //加载时实例化单例,static final类型
       private static final Singleton INSTANCE = new Singleton();  
    }
    //私有构造器  
    private Singleton (){}
    //通过调用getInstance实例化静态内部类,返回静态内部类加载时实例化的单例  
    public static final Singleton getInstance() {  
       return SingletonHolder.INSTANCE;  
    }  
}  
内部类方式既实现了线程安全,又避免了同步带来的性能影响。这种方式利用了classloder的机制来保证初始化instance时只有一个线程,这种方式情况下,Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显式通过调用getInstance方法时,才会显式装载SingletonHolder类,从而实例化instance。如果实例化instance很消耗资源,让他延迟加载是很有帮助的。

第二种 饿汉式单例

package com.xiaokai.constructor.singleton;

/**
 * Created by Administrator on 2016/8/4.
 */

public class Singleton2 {
    private Singleton2() {
    }
    //在类初始化时,实例化单例
    private static final Singleton2 instance = new Singleton2();

    //静态工厂方法
    public static Singleton2 getInstance() {
        return instance;
    }
}
单元测试类
package com.xiaokai.constructor.singleton;

import org.junit.Assert;
import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Created by Administrator on 2016/8/4.
 */
public class Singleton2Test {
    @Test
    public void getInstance() throws Exception {
        Singleton2 singleton1 = Singleton2.getInstance();
        Singleton2 singleton11 = Singleton2.getInstance();
        Assert.assertSame(singleton1, singleton1);
    }

}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

第三种 枚举式单例(强烈推荐)

package com.xiaokai.constructor.singleton;

/**
 * Created by Administrator on 2016/8/4.
 */
public enum Singleton3 {
    INSTANCE;

    public void whateverMethod() {
    }
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

有两个问题需要注意:
 1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类  装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
 2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

解决第一个问题的方法是重写Object的getClass方法。
    private static Class getClass(String classname)
            throws ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        //先判空,再加载单例
        if (classLoader == null)
            classLoader = Singleton3.class.getClassLoader();

        return (classLoader.loadClass(classname));
    }
解决第二个问题的方法是实现序列化接口后重写readResolve方法。
public class Singleton implements java.io.Serializable {
    public static Singleton INSTANCE = new Singleton();

    protected Singleton() {

    }

    private Object readResolve() {
        return INSTANCE;
    }
}

总结

三类:懒汉(懒汉又有双重校验锁,静态内部类),饿汉,枚举。
懒汉:需要加锁才能实现多线程同步,但是效率会降低。优点是延时加载。
双重校验锁:麻烦,在当前Java内存模型中不一定都管用,某些平台和编译器甚至是错误的,在JDK1.5之后,才能够正常达到单例效果。。
静态内部类:延迟加载,减少内存开销。因为用到的时候才加载,避免了静态field在单例类加载时即进入到堆内存的permanent代而永远得不到回收的缺点(大多数垃圾回收算法是这样)。
饿汉:因为加载类的时候就创建实例,所以线程安全(多个ClassLoader存在时例外)。缺点是不能延时加载。
枚举:很好,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。但是失去了类的一些特性,没有延迟加载。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值