单例模式

单例模式是指,在一个类在任何情况下都只有一个实例,并且提供一个全局访问点。

单例模式要隐藏构造方法,属于创建型模式。

常见的单例模式有四种:饿汉式单例,懒汉式单例,注册式单例,ThreadLocal单例。

1,饿汉式单例

在单例类首次加载的时候就创建实例。

缺点:浪费内存。

package com.gupaoedu.vip.pattern.singleton.hungry;

/**
 * Created by Tom.
 */

//饿汉式单例
// 它是在类加载的时候就立即初始化,并且创建单例对象

    //优点:没有加任何的锁、执行效率比较高,
    //在用户体验上来说,比懒汉式更好

    //缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间
    //浪费了内存,有可能占着茅坑不拉屎

    //绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题
public class HungrySingleton {
    //先静态、后动态
    //先属性、后方法
    //先上后下
    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return  hungrySingleton;
    }
}
package com.gupaoedu.vip.pattern.singleton.hungry;

/**
 * Created by Tom.
 */

//饿汉式静态块单例
public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungrySingleton;
    static {
        hungrySingleton = new HungryStaticSingleton();
    }
    private HungryStaticSingleton(){}
    public static HungryStaticSingleton getInstance(){
        return  hungrySingleton;
    }
}

2,懒汉式单例

在被外部类调用时才创建实例。

要考虑多线程安全,反射机制和序列化对单例的破坏。

a:反射机制破坏:构造方法内部判断是否抛异常。

b:序列化破坏:重写readResolve()方法。(枚举式单例无需此步骤,本身就可以防止序列化的破坏)

我们这里给出3种写法。其中,第二种为推荐写法。

package com.gupaoedu.vip.pattern.singleton.lazy;

/**
 * Created by Tom.
 */

//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;
    public synchronized static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}
package com.gupaoedu.vip.pattern.singleton.lazy;

/**
 * Created by Tom.
 */

//懒汉式单例


//这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
//完美地屏蔽了这两个缺点
//史上最牛B的单例模式的实现方式
public class LazyInnerClassSingleton {
    //默认使用LazyInnerClassGeneral的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyInnerClassSingleton(){
    // 为避免反射机制破坏单例
        if(LazyHolder.LAZY != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyInnerClassSingleton getInstance(){
        //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }

    //默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}
package com.gupaoedu.vip.pattern.singleton.lazy;

/**
 * Created by Tom.
 */

public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazy = null;

    private LazyDoubleCheckSingleton(){}
    public static LazyDoubleCheckSingleton getInstance(){
        if(lazy == null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazy == null){
                    lazy = new LazyDoubleCheckSingleton();
                    //1.分配内存给这个对象
                    //2.初始化对象
                    //3.设置lazy指向刚分配的内存地址
                    //4.初次访问对象
                }
            }
        }
        return lazy;
    }
}

3,注册式单例

将每一个实例都缓存到一个容器中,使用唯一标识获取实例。包含枚举式和容器式 两种形式。

1,序列化过程中,他的底层对枚举进行了处理,避免了序列化对枚举式单例的破坏。

2,在jdk层面,反射时检查了是否是枚举,是的话会抛异常,来组织对单例的破坏。

package com.gupaoedu.vip.pattern.singleton.register;

/**
 * Created by Tom.
 */
//常量中去使用,常量不就是用来大家都能够共用吗?
//通常在通用API中使用
public enum EnumSingleton {
    INSTANCE;
    private Object data;
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

 

package com.gupaoedu.vip.pattern.singleton.register;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Tom.
 */

//Spring中的做法,就是用这种注册式单例
public class ContainerSingleton {
    private ContainerSingleton(){}
    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
//ConcurrentHashMap自己内部是线程安全的,即put方法。这里的锁是帮助getInstance实现线程安全。
    public static Object getInstance(String className){
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

4,ThreadLocal伪线程安全,线程内部是安全的,跨线程不安全。本质上属于注册式单例。

用来多数据源动态切换。(暂时不作为重点)

package com.gupaoedu.vip.pattern.singleton.threadlocal;

/**
 * Created by Tom.
 */
public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            new ThreadLocal<ThreadLocalSingleton>(){
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton(){}

    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
}

单例模式

优点:1在内存中只有一个实例,减少了内存开销;2可以避免对资源的多重占用;3设置全局访问点,严格控制访问。

缺点:1,没有接口,扩展很困难;2要扩展单例对象,只能修改代码。

 

总结:

1,私有化构造器

2,保证线程安全

3,延迟加载

4,防止序列化和反序列化破坏单例

5,防御反射攻击单例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值