单例模式是23种设计模式中比较常用的模式之一,它的定义是一个类只有一个实例,并提供一个全局访问点。就是在应用环境中一个类只能创建一个对象实例,这样处理是因为有些场景如:线程池、缓存、日志对象、打印机对象等只能有一个实例,若有多个,则会产生程序、资源等等问题。
单例模式的创建方法有两种:懒汉式和饿汉式
一.延迟实例化方式(懒汉式)(非线程安全)
单例模式的实现方式很简单,即将构造器设为私有(private),不允许其他类通过new方式创建对象,并对外提供一个返回该类实例的接口。具体代码如下:
package com.singleton.lazy;
public class Singleton {
private static Singleton singleton;
//私有化构造方法
private Singleton (){
}
//提供公有化访问点
public Singleton getSingleton(){
if(singleton==null){ //判断是对象否存在
singleton=new Singleton();
}
return singleton;
}
}
该类将构造器设为private,则只有该类内部可以通过new创建实例,同时对外提供getSingleton()方法,调用该方法返回一个实例。(若实例未创建,则创建,创建后始终返回该实例) 此种方式在需要该类实例时才会创建,可以称之为“延迟实例化“的方式(也称为“懒汉模式“)。另外重要的是,此种方法是非线程安全的。 试想若有2个线程同时到达 if(singleton==null)的判断中,并且都得到true,则就会产生两个实例,破坏单例模式的约束。
若想在多线程环境中应用单例模式,还需要对刚才的方法加以改变,将创建的实例看做共享的资源,当需要创建资源时加锁控制访问,操作完成后释放锁。
package com.singleton.lazy;
public class Singleton2 {
private volatile static Singleton2 singleton;
private Singleton2(){
}
public static Singleton2 getSingleton(){
if(singleton==null){ //检查实例,若未创建进入同步块
synchronized(Singleton.class){
//同步块开始
if(singleton==null){ //进入同步块后再判断一次,防止出现2个线程同时通过第一次检 查的情况
singleton= new Singleton2();
}
} //同步块结束
}
return singleton;
}
}
volatile 的意思就是:告诉虚拟机,对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。 Singleton= new Singleton2()这句话不是原子操作,其主要包含3步:
1. 给Singleton2分配空间;
2. 初始化Singleton2的构造函数;
3. 将分配的空间地址返回给实例。
java编译器允许处理器乱序执行以提高执行效率,因此在实际执行中可能是1-2-3,也可能是1-3-2。若是1-3-2,若当线程1执行完3后轮到线程2执行,此时线程1由于执行了3,singleton已不为空,而未执行构造函数等操作,因此得到的为不完整的实例,线程2使用该实例时就会出错。
二.初始实例化(饿汉模式)
以上使用线程加锁的方式可以解决多线程的方式,不过有些麻烦,还要考虑性能的一些影响。JVM可以保证一个类只被加载一次,因此可以考虑下面的方法
package com.singleton.lazy;
public class Singleton {
private Singleton(){}
private static Singleton singleton = new Singleton();
public Singleton getSingleton(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
此种方法在类被初次记载时即初始化了该类实例,不存在线程安全问题。
饿汉式的缺点是类一加载就实例化,提前占用系统资源。