设计模式之单例模式

普通的java类的反序列化过程中,会通过反射调用默认的构造函数来初始化对象,所以即使单例中的构造函数是私有的,也会被反射给破坏掉,由于反序列化后的对象是重新new出来的,所以这就破坏了单例,而我们知道,解决单例的并发问题,其实就是解决初始化过程中线程安全的问题,在序列化的时候,java仅仅是将枚举对象的name属性输出到结果中,反序列话的时候则是通过java.lang.Enum的valueOf方法,根据名字查找枚举对象,所以枚举实现的单例天生是线程安全的

在开头我想说单例模式最安全最简单的方法是使用Enum,因为我们用的双重校验的方式虽然线程安全,但是还是有问题的,因为它无法解决反序列化破坏单例的问题,其实使用枚举只不过线程安全的保证不需要我们关心而已,它在底层已经做了安全方面的保证而已,我们将定义好的枚举反编译,其实就能发现,其实枚举在经过 javac编译以后,会被转换成 static来定义的,如:public final class T extends Enum   反编译后的胆码为:

public final class T extends Enum{ 
public static final T SPRING; 
public static final T SUMMER; 
public static final T AUTUMN; 
public static final T WINTER; 
private static final T ENUM$VALUES[]; 
static { SPRING = new T("SPRING", 0); 
SUMMER = new T("SUMMER", 1); 
AUTUMN = new T("AUTUMN", 2); 
WINTER = new T("WINTER", 3); 
ENUM$VALUES = (new T[] { SPRING, SUMMER, AUTUMN, WINTER }); 
}}


了解JVM的类加载机制的朋友应该对这部分比较清楚。static类型的属性会在类被加载之后被初始化,我们在深度分析Java的ClassLoader机制(源码级别)中介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的(因为虚拟机在加载枚举的类的时候,会使用ClassLoader的loadClass方法,而这个方法使用同步代码块保证了线程安全)。所以,创建一个enum类型是线程安全的。



饿汉式

public class EagerSingleton{

        private static final EagerSingleton instance = new EagerSingleton();

        private EagerSingleton(){};

        public static final EagerSingleton getInstance(){

            return instance;

    }

        

}

饿汉式是由于在申明的同时立即初始化,所以即使在高并发的情况下,依然可以保证单例


//懒汉式
public class LazySingleton {

private static LazySingleton instance;

private LazySingleton() {

}

public static LazySingleton getInstance() {

if(instance==null) {
instance = new LazySingleton();
}
return instance;
}

考虑到反射:

·

实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问

但是不能阻止反射,如果在第一次调用getInstance()方法之前实施反射入侵,那么反射创建出来的实例就就跟如图所示的,

不是一个实例,所以说反射会打破单例的情况,反之,如果在调用getInstanc()方法之后,就可以阻止反射的入侵


考虑到多线程:由于在getInstance()时,才会初始化对象,那么在高并发的情形下,同时多个线程调用getInstance()方法,就可以成功的构造多个对象,从而打破单例,有一种DCL双锁检测机制,就是专门用来处理这种情况的,为什么是双锁机制呢,只加一个synchronized不行吗?

     单独使用一个synchronized,理论上是可以禁止多个线程情况下创建多个实例,但是还是需要考虑到另外的一种情况,指令重排。

    java代码中,在jvm中最终都会转化成为一系列的二进制的操作指令,而jvm为了优化指令的执行,可能会进行一些重新排序,从而使代码并不像我们所见的形式自上而下执行,java语言提供了许多禁止指令重排的方式,在这里给instance加上另外一层限制:volitile,则是最好的解决办法

public class  LazySingleton{

    private static volatile LazySingleton instance;

    private LazySingleton(){}


    public static synchronized LazySingleton getInstance(){

    if(instance==null){

            instance = new LazySingleton();

        }

    return instance

    }

}


相对于懒汉式跟饿汉式,静态内部类模式,我们使用了一个私有的静态内部类,来存储外部类的单例,这种静态内部类,一般称为Hoder,而利用静态内部类的特征,外部类的getInstance()方法,可以直接指向Hoder持有的对象

package jdkdynamic;
/** 静态内部类实现单例模式
 * 
 * 由于在调用SingletonHoder.instance的时候,才会对单例进行初始化,而且通过反射,是不会从外部类
 * 获取内部类的属性的,所以说通过这种形式,很好的避免了反射入侵
 * 
 * 考虑多线程:
 * 由于静态内部类的特性,只有再其被第一次引用的时候才会被加载,所以可以保证其线程的安全
 * 
 * 它兼顾了饿汉式的安全性(不会被反射入侵)
 * 
 *  缺点:需要两个类去做这一点,虽然不会创建静态内部类的对象,但是其Class对象还是会被创建,而且是属于永久代的对象
 * */
public class StaticInnerSingleton {

private StaticInnerSingleton() {
if(SingletonHoder.instance !=null) {

throw new IllegalStateException();
}
}

private static  class SingletonHoder{

private static StaticInnerSingleton instance = new StaticInnerSingleton();
}



public static StaticInnerSingleton getInstance() {
return SingletonHoder.instance;
}
}



package com.roncoo.eshop.inventory.thread;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


import com.roncoo.eshop.inventory.request.Request;


/**
 * 单例的线程池
 * @author 123456
 *
 */
public class RequestProcessorThreadPool {
/**
* 在实际的项目中,你设置的线程池的大小是多少,每个线程控制的那个队列大小是多少
* 都可以配到一个外部的配置文件里面,这里是直接写死了
*/
private ExecutorService threadpool =Executors.newFixedThreadPool(10);

//内存队列
private List<ArrayBlockingQueue<Request>> queues = new ArrayList<ArrayBlockingQueue<Request>>();

public RequestProcessorThreadPool() {
System.out.println("这是构造方法====================================");
//在初始化线程池的时候就创建好10个队列,容量初始化为100
for(int i=0;i<10;i++) {
ArrayBlockingQueue<Request> queue = new ArrayBlockingQueue<Request>(100);
queues.add(queue);
threadpool.submit(new WorkThread(queue));
}
}

/**
* 采用静态内部类的方式实现单例是绝对线程安全的一种方式
* @author 123456
*
*/
private static class Singleton{

private static RequestProcessorThreadPool instance;

static {
instance = new RequestProcessorThreadPool();
}

public static RequestProcessorThreadPool getInstance() {
return instance;
}
}
/**
* 无论你多少个线程并发的调用getInstance()这个方法,一定有一个线程去调用Singleton.getInstance();
* 这个内部类就会初始化
* 这里利用了jvm的机制去保证多线程的并发执行
* 内部类的初始化,一定只会发生一次,不管多少个线程并发的去初始化,他们都会拿到第一次初始化的那个instance
* 实例,而且都是同一个实例,保证了单例的实现
* @return
*/
public static RequestProcessorThreadPool getInstance() {
System.out.println("我是getInstance()方法===================================");
return Singleton.getInstance();
}

public static void  init() {
System.out.println("我是初始化方法============================================");
getInstance();
}


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值