单例模式懒汉饿汉式

N久之前做的笔记,现在搬迁
和曾丹模拟的时候,讲到单例模式,她问我为什么叫懒汉?为什么叫饿汉?

懒汉式:就是非常懒,只有当你要用他的时候,他才会去行动,去产生实例类加载时不初始化
在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢

package Singleton;

public class LazySingleton {
   //懒汉式单例模式
   //比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
   
   
   private static LazySingleton intance = null;//静态私用成员,没有初始化
   
   private LazySingleton()
    {
       //私有构造函数
    }
   
   public static synchronized LazySingleton getInstance()   //静态,同步,公开访问点
    {
       if(intance == null)
        {
            intance = new LazySingleton();
        }
       return intance;
    }
}

 

//

饿汉式:饿汉就是我不能等待,创建类的时候就拥有实例类加载时就完成了初始化,所以类加载会比较慢,而获取对象的速度很快

package Singleton;

public class EagerSingleton {
   //饿汉单例模式
   //在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
   
   private static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化
   
   private EagerSingleton() 
    {
       //私有构造函数
    }
   
   public static EagerSingleton getInstance()   //静态,不用同步(类加载时已初始化,不会有多线程的问题)
    {
       return instance;
    }
   
   
}


单例模式特点:

1)私有构造函数

2)静态私有成员

3)公开访问点getInstance

饿汉懒汉的区别:
饿汉在类加载的时候就加载了,懒汉只有在使用的时候才会返回

在并发的情况下,懒汉式是不安全的。如果两个线程,我们称它们为线程A和线程B,在同一时间调用getInstance()方法,如果线程A先进入if块,此时instance是为null,然后线程B进入if块,并且创建实例,B退出,A再次进入,A再次实例化的值会覆盖B实例化的值

---
//

对懒汉式的同步有几种方式

synchronized代码块在AsyncTask的应用

private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }


synchronized代码块的双重检测
 

//用volatile防止指令重排,不是很懂,需要后面再看
private volatile static Handler sHandler;

public static Handler getHandler() {
        if(sHandler == null)
        {
            synchronized (AsyncTask.class) {
                if (sHandler == null) {
                    sHandler = new InternalHandler();
                }
                return sHandler;
            }
        }
    }

同步方法

public static synchronized Singleton getInstance()   //静态,同步,公开访问点
    {
       if(intance == null)
        {
            intance = new Singleton();
        }
       return intance;
    }


lock双重判断
 

public static Singleton getInstance(){  
        if(intance == null){  
            lock.lock();  
            try {  
                if(intance == null){  
                    intance = new Singleton();  
                }  
            } finally{  
                lock.unlock();  
            }  
        }
        return intance;  
    }  

---

/
- 双重检查锁定模式(并行模式)
    - 为什么要双重检测
        - 同步的代价很高,会降低100倍或者更高的性能(Boehm, Hans-J. "Threads Cannot Be Implemented As a Library", ACM 2005, p265)所以为了减少不必要的同步,要验证锁定条件

    - 双重检查锁定模式首先验证锁定条件(第一次检查),只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)。
    - 其实就是线程A发现变量没有被初始化才需要加锁同步去创建实例,如果说已经初始化了,就只要调用就好了。
    - 为什么同步块内还需再做一次检测呢?举个例子,线程A访问到if块时被线程B挤下去了,然后
    线程B获取锁并创建实例退出,线程A再次获取锁,检测instance是否为null,此时可以避免线程B所创建的值。所以,double check的目的才是避免线程不安全。single check只是为了提高性能。
    - 至于这里采用指令重排,不是很懂
    
    
静态内部类创建实例

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}


为什么要使用静态内部类来创建单例模式呢?饿汉已经很简便了,但饿汉式在类加载时就会初始化,不管用不用都会占用内存,所以采用静态内部类来完善。
 
想想以前陈老师讲过,C++的单例模式也是靠这种方式来释放instance的内存的。肯定不能再析构函数中释放。


其实这种写法也比较麻烦,虽然创建静态内部类没什么不好?枚举,最为简单。默认线程安全并且可以防止反序列化重新创建对象。不能反序列化这个倒是可以解释:

public enum EasySingleton{
    INSTANCE;
}

因为序列化反序列都是针对对象的,被 static 修饰的变量就不能序列化。而枚举等同于实现static
、final的功能。所以不能序列化是可以解释的。

package com.oreilly.tiger.ch03;
public enum Grade {
  A, B, C, D, F, INCOMPLETE
};

 

public class OldGrade {
  public static final int A = 1;
  public static final int B = 2;
  public static final int C = 3;
  public static final int D = 4;
  public static final int F = 5;
  public static final int INCOMPLETE = 6;
}


至于枚举类为什么是线程安全的?
java类的加载和初始化过程都是线程安全的

阅读更多,点击一下链接:
- 维基百科-双重检查锁定模式

    https://zh.wikipedia.org/wiki/%E5%8F%8C%E9%87%8D%E6%A3%80%E6%9F%A5%E9%94%81%E5%AE%9A%E6%A8%A1%E5%BC%8F

- 如何正确地写出单例模式

    http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/

- 枚举类型入门-IBM

    http://www.ibm.com/developerworks/library/j-enums/j-enums-pdf.pdf
    
- 深度分析Java的ClassLoader机制(源码级别)    

    http://www.hollischuang.com/archives/199
- Java类的加载、链接和初始化

     http://www.hollischuang.com/archives/201

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值