Java的23种设计模式(1)单例模式

单例模式

不再手动创建对象,通过调用方法获取全局唯一的实例.

饿汉式

类一加载,实例就被创建,且只创建一次,线程安全,高效,但长时间不使用,浪费资源,

public class Hungry {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];
    
    //私有构造函数,不能被外部所访问
    private Hungry(){
    }
    private  static Hungry HUNGRY = new Hungry();
    
    //返回单例对象
    public static Hungry getInstance(){
         return HUNGRY;
    }
}

懒汉式

public class LazyTest {
    private static LazyTest instance = null;
    private LazyTest() {
    }
    //效率较低
    public static  LazyTest getInstance1() {
        if (instance == null) {
            instance = new LazyTest();
        }
        return instance;
    }
}

延迟加载,节省资源,但线程不安全,可能创建出多个对象.

优化:加锁

  • 方法同步,线程安全,但效率低
  • 同步代码块,只锁new语句,效率稍高,但仍存在线程安全
  • DCL双重检测锁定

DCL 懒汉式

DCL(double-checked-locking)双重检测锁定

public class LazyTest {
  //volatile 禁止指令重排
  private static volatile LazyTest instance = null;
  private LazyTest(){
  }
  public static LazyTest getInstance2(){
      if(instance == null){
          synchronized (LazyTest.class){ 
              if(instance == null){
                  instance = new LazyTest();
              }
          }
      }
      return instance;
  }
}
  • 如果对象已存在,就直接返回对象,不会进行锁判定,比方法锁效率高.
  • new 创建对象不是一个原子性操作,可能发生指令重排.例如:线程A执行了13还没构造,线程B这时if判断不为null但对象还没初始化,就会发生问题.所以要使用volatile禁止指令重排.
    • 1.分配内存空间
    • 2.执行构造方法,初始化对象
    • 3.把这个对象指向这个空间
  • 使用反射可以破坏DCL懒汉式的单例
//一个使用getInstance方法创建对象,一个使用反射创建对象
public static void main(String[] args) throws Exception {
    LazyTest  instance = LazyTest .getInstance();
    
    Constructor<LazyTest > declaredConstructor =LazyTest .class.getDeclaredConstructor(null);
    
    declaredConstructor.setAccessible(true);
    
    LazyTest instance2 = declaredConstructor.newInstance();
    
    System.out.println(instance);
    
    System.out.println(instance2);
}

解决: 在构造函数中判断是否已经创建过对象

  private LazyTest(){
      synchronized (LazyTest.class){ 
            if(instance != null){
                throw new RuntimeException("不要试图使用反射创建对象");
            }
       }
  }

但这时还是可以使用来反射创建两个对象.

解决:使用信号量来判断

private static boolean flog = false;
private LazyTest(){
    synchronized (LazyTest.class){
        if (flog == false){
            flog = true;
        }else {
            throw new RuntimeException("不要试图使用反射破坏异常");
        }
    }
}

如果逆向分析出信号量仍然可以创建两个对象.这时只能使用枚举类,因为枚举不能使用反射的newInstance()创建对象.

枚举式

因为枚举构造函数只会执行一次的特性,所以其本身就是单例的.

public class EnumTest {
    private EnumTest() {
    }
    public static EnumTest getInstance(){
        return Test.INSTANCE.instance;
    }
    //通过枚举类创建对象
    private enum Test{
        INSTANCE; //通过INSTANCE调用枚举类的方法变量
        private  EnumTest instance=null;
       //枚举类不允许声明 public的构造函数
        Test() {
            instance = new EnumTest();
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值