单例设计模式
确保某一个类只有一个实例,而且自行实例化 向整个系统提供这个实例。
懒汉式(线程不安全)
public class Cart {
private static Cart a=null;
private Cart(){}
public static Cart getCart(){
if (a==null) {
a=new Cart();
}
return a;
}
该懒汉式在低并发的情况下尚不会出现问题,若系统压力增大,并发量增加时则可能在内存中出现多个实例,破坏了最初的预期。为什么会出现这种情况呢?如一个线程A执行到a= new Cart(),但还没有获得对象(对象初始化是需要时间的),第二个线程B也在执行,执行到(a == null)判断,那么线程B获得判断条件也是为真,于是继续运行下去,线程A获得了一个对象,线程B也获得了一个对象,在内存中就出现两个对象!
懒汉式(线程安全)
public class Cart {
private static Cart a=null;
private Cart(){}
public static synchronized Cart getCart(){
if (a==null) {
a=new Cart();
}
return a;
}
}
该懒汉式用Synchronized来解决线程不安全问题,将Synchronized加入到被调用的方法中,使其达到线程安全的目的。
Synchronized关键字作用
同步方法支持一种简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。
即:能够保证在同一时刻最多只有一个线程执行该段代码,达到保证并发安全的效果。
Synchronized是Java的关键字,被Java原生支持。
是最基本的互斥同步手段。
饿汉式
public class Cart {
private static Cart a=new Cart();
private Cart(){}
public static Cart getCart(){
return a;
}
}
这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。 这种方式基于类加载机制避免了多线程的同步问题,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到懒加载的效果。
双重检查模式(DCL)
public class Cart {
//懒汉模式 双重检查锁定DCL(double-checked locking)
//缺点:由于jvm存在乱序执行功能,DCL也会出现线程不安全的情况。(DCL失效问题)
// jdk1.6及之后,只要定义为private volatile static Cart a 就可解决DCL失效问题。
// volatile确保a每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅。
// volatile可以保证即使java虚拟机对代码执行了指令重排序,也会保证它的正确性。
private volatile static Cart a;//延迟加载,需要时才创建实例
private Cart(){}
public static Cart getCart() {
if (a== null) {
synchronized (Cart.class) {//只要锁住new即可,不需要放在外面getInstance()方法上
if (a== null) { //2此判断,防止线程A创建实例时,线程B也进来创建实例
a= new Cart();
}
}
}
return a;
}
}
静态内部类单利模式
public class Cart {
private Cart() {
}// 私有化构造器
public static Cart geta() {// 对外提供访问
return A.a;
}
private static class A {// 写一个静态内部类,实例化外部类
private static final Cart a = new Cart();
}
}
第一次加载Cart类时并不会初始化a,只有第一次调用geta方法时虚拟机加载A 并初始化a,这样不仅能确保线程安全也能保证Cart类的唯一性
枚举单例
public class Cart {
public static Cart getas() {
return USA.AS.getas();
}
private enum USA {
AS;
private Cart as;
USA() {
as = new Cart();
}
private Cart getas() {
return as;
}
}
}
枚举创建对象的线程是安全的,并且任何时候都是单例
虽然上面的的方法线程很安全,但是反序列化可以使其重新创建对象,以下方法能杜绝反序列化重新创建对象并防止反射获取多个对象的漏洞:
import java.io.ObjectStreamException;
import java.io.Serializable;
public class SingletonDemo6 implements Serializable{
// 类初始化时,不初始化这个对象(延迟加载,真正用的时候再创建)
private static SingletonDemo6 instance;
private SingletonDemo6() {
// 防止反射获取多个对象的漏洞
if (null != instance) {
throw new RuntimeException("单例模式被侵犯!");
}
}
public static synchronized SingletonDemo6 getInstance() {
if (null == instance)
instance = new SingletonDemo6();
return instance;
}
// 防止反序列化获取多个对象的漏洞。
// 无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。
// 实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
基于HaspMap实现容器单例模式
import java.util.HashMap;
import java.util.Map;
public class Cart {
private Cart() {
// TODO Auto-generated constructor stub
}
private static Map <String,Object> map=new HashMap<>();
public static void putinstance(String key,Object value){
if (key!=null&&!key.isEmpty()&&value!=null) {
if (!map.containsKey(key)) {
map.putIfAbsent(key, value);
}
}
}
public static Object getinstance(String key){
return map.get(key);
}
}
如不考虑,序列化和反射攻击问题,容器单例模式还是有一定的使用场景的。如果程序中单例对象非常多,可以使用容器对所有单例对象进行统一管理。
基于ThreadLocal实现容器单例模式
public class ThreadLocalCart {
private static final ThreadLocal<ThreadLocalCart> threadLocalInstance = new ThreadLocal<ThreadLocalCart>() {
protected ThreadLocalCart initialValue() {
return new ThreadLocalCart();
}
};
private ThreadLocalCart() {
}
public static ThreadLocalCart getInstance() {
return threadLocalInstance.get();
}
}