单例模式就是将类的构造函数进行private化,然后只留出一个静态的Instance函数供外部调用者调用。
一、饿汉式
饿汉式单例,不管以后用不用这个对象,我们一开始就创建这个对象的实例,在JVM中对类的加载跟初始化,由虚拟机保证线程的安全, 需要的时候就返回已创建好的实例对象,所以比较饥饿,故此叫饿汉式单例
public class SingletonHanger {
private static final SingletonHanger instance = new SingletonHanger();
private SingletonHanger() {
}
public static SingletonHanger getInstance(){
return instance;
}
二、懒汉式
当我们类有太多变量的时候就不适合饿汉式创建类,在我们需要的时候才进行类初始化,这种模式叫懒汉式。比如下面的静态类初始化模式。
public class SingleInit {
private SingleInit(){}
// 定义一个私有类,来持有当前类的实例
private static class InstanceHolder{
public static SingleInit instance = new SingleInit();
}
public static SingleInit getInstance(){
return InstanceHolder.instance;
}
}
三、实际的懒汉式
第一版:多线程不安全的模式,只是简单的判断是否初始化了,没初始化则初始化。
class SingletonLazy{
private static SingletonLazy instance = null;
private SingletonLazy() {
}
/**
* 此方法实现的单例,无法在多线程中使用,多线可以同时进入if方法,会导致生成多个单例对象。
* @return
*/
public static SingletonLazy getInstance(){
if(instance==null){
instance = new SingletonLazy();
}
return instance;
}
}
第二版:多个线程进入getInstance的时候,进行抢锁,但是这种方法不可取,严重影响性能,每次调用都要获得锁,严重影响性能。
class SingletonLazy{
private static SingletonLazy instance = null;
private SingletonLazy() {
}
public static synchronized SingletonLazy getInstance(){
if(instance==null){
instance = new SingletonLazy();
}
return instance;
}
}
第三版:双重加锁机制,有两个判断
1. 调用的时候先判断是否已经非空了,如果初始化好了则直接返回。
2. 没初始化好 AB两个线程都获得类锁,A获得B没获得,然后A直接将对象创建,释放锁。但是如果此时A初始化的对象没有赋值给主内存的singleDcl,这个时候B获得锁了仍然会进行创建 如果不进行if判断,索引要加if判断同时还要将主变量设置为volatile。因为我们new 出来的对象如果对象里包含很大的数据内容肯能数据还没有创建好的哦!,所以要用volatile。
public class SingleDcl {
private volatile static SingleDcl singleDcl; //保证可见性
private SingleDcl(){
}
public static SingleDcl getInstance(){
if(singleDcl==null) { // 放置进入加锁代码,先判断下是否已经初始化好了
synchronized (SingleDcl.class) { // 类锁 可能会出现 AB线程都在这卡着,A获得锁,B等待获得锁。
if(singleDcl==null) {
// 如果A线程初始化好了,然后通过vloatile 将变量复杂给住线程。
// 如果此时没有singleDel === null,判断 B进程 进来后还会再次执行 new 语句
singleDcl = new SingleDcl();
}
}
}
return singleDcl;
}
}
三、枚举类
使用枚举实现单例模式(花样玩枚举),也是Effective Java中推荐使用的方式根据具体情况进行实例化,在初始化的时候已经给我们知道好几个实际类的类型了。它的好处:更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使面对复杂的序列和反射攻击。
enum SingletionEnum{
SingletionEnum("单例的枚举方式");
private String str ;
private SingletionEnum(String str){
this.setStr(str);
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}