手写单例模式:
一、为什么要有单例模式:
在编程中,有些场景,是这样的:
有些对象我们只需要一个,比如线程池对象、缓存、系统全局配置文件对象等。
这样我们就保证一个在全局使用的类不被频繁的创建与销毁,节省系统资源。
二、实现单例模式的三大要点:
1、首先要确保全局只有一个类的实例。
因此至少类的构造器要私有化。
2、单例的类只能自己创建自己的实例。
因为构造器私有了,但是还要有一个实例,所有就只能自己创建。
3、单例类必须能够提供自己的唯一实例给其他类。
因此要有一个公共的方法能返回该单例类的唯一实例。
三、单例的两大类六种实现:
1、懒汉模式(单线程使用)
–懒汉式就是不在系统加载时就创建类的单例,而是在第一次使用实例的时候再创建。
–多线程禁止使用。
延迟加载(LazyLoading)很明显
严格来讲不算单例模式,因为线程不安全,多线程下,可能产生多个对象。不是单例。
public class Lanhandanli{
//第一步静态私有化对象
private static Lanhandanli instance = null;
//第二步私有化空参构造
privateLanhandanli(){}
//第三步提供获取对象方法
piublic static Lanhandanli getInstance(){
//判断对象是否为null,如果为空就创建
if(instance==null){
instance = new Lasnhandanli();
}
return instance;
}
}
2、饿汉模式–静态常量方式
–在加载类的时候就会创建类的单例,并保存在类中。
–静态常量方式(线程安全–类加载是就初始化实例,避免了多线程同步问题。天然线程安全)
public class Ehandanli{
private static finall Ehandanli instance = new Ehandanli();
private Ehandanli(){}
public static Ehandanli getInstance(){
return Ehandanli;
}
}
3、饿汉模式–静态代码块方式
–静态代码块方式(线程安全)
其实就是上面静态常量饿汉式 实现上稍微变动一下,将类的实例化放到了静态代码块中而已。
public class Ehandanli{
private static finall Ehandanli instance;
static {
instance = new Ehandanli();
}
private Ehandanli(){}
public static Ehandanli getInstance(){
return Ehandanli;
}
}
4、懒汉模式–synchronized同步锁
–synchronized同步锁(线程安全–与上面懒汉模式实现唯一不同的是,获取实例getInstance()方法上加了同步锁,保证线程多线程下单例。但是效率有所折损,不过还好)
public class Lanhandanli{
//第一步静态私有化对象
private static Lanhandanli instance = null;
//第二步私有化空参构造
privateLanhandanli(){}
//第三步提供获取对象方法
piublic static synchronized Lanhandanli getInstance(){
//判断对象是否为null,如果为空就创建
if(instance==null){
instance = new Lasnhandanli();
}
return instance;
}
}
5、懒汉模式–双重校验锁–DCL
–双重校验锁–DCL,即 double-checked locking
使用到(volatile and synchronized)
此种实现中不用每次需要获得锁,减少了获取锁和等待的事件。
注意volatile关键字的使用,保证了各线程对singleton静态实例域修改的可见性。
public class Lanhandanli{
//第一步静态私有化对象
private volatile static Lanhandanli instance = null;
//第二步私有化空参构造
privateLanhandanli(){}
//第三步提供获取对象方法
piublic static synchronized Lanhandanli getInstance(){
//判断对象是否为null,如果为空就创建
if(instance==null){
instance = new Lasnhandanli();
}
return instance;
}
}
描述:这种方式称为双重检查锁(Double-Check Locking),需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,
需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,
且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,
可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
6、懒汉模式–静态内部类实现单例
–静态内部类实现单例(线程安全、效率高)
zhe这种方式下Singleton类被加载了,inatance不一定被初始化。因为SingletonHolder累没有被主动使用,
只有通过显示调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance.
注意内部类SingletonHolder要用static修饰且其中的的静态变量instance必须睡final修饰的。
public calss Singleton{
//内部类
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
private Singleton(){};
public static final Singleton getInstance(){
return SingletonHolder.instance;
}
}
描述:饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,
而且性能受影响。可见,无论是饿汉式单例还是懒汉式单例都存在这样那样的问题,有没有一种方法,
能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是:Yes!
下面我们来学习这种更好的被称之为Initialization Demand Holder (IoDH)的技术。
在IoDH中,我们在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,
再将该单例对象通过getInstance()方法返回给外部使用。由于静态单例对象没有作为Singleton的成员变量直接实例化
,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类SingletonHolder,在该内部类中定义
了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变
量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。通过使用IoDH,
我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式**
(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH)