单例模式


/**
* 懒汉式单例1 * 事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效 * 没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton1 实例 */ public class Singletion1 { //第一步先将构造方法私有化 private Singletion1(){} // 然后声明一个静态变量保存单例的引用 private static Singletion1 singletion1 = null; //通过提供一个静态方法来获得单例的引用 public static Singletion1 getInstance(){ if (singletion1==null){ singletion1 = new Singletion1(); } return singletion1; } }

 测试:

public class ExectorThread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+":"+singleton);
    }
}
public class LazySimpleSingletonTest {
    public static void main(String[] args){
        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("end");
        //end
        //Thread-0:com.vip.singleton.lazy.LazySimpleSingleton@c6dfc63
        //Thread-1:com.vip.singleton.lazy.LazySimpleSingleton@29b4bc88

//        一定几率出现创建两个不同结果的情况,意味着上面的单例存在线程安全隐患
    }
}

 

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式

懒汉式单例的特点是:被外部类调用的时候内部类才会加载

/**
 *懒汉式单例.保证线程安全
 *
 */
public class Singletion2 {
    private Singletion2() {}

    private static Singletion2 singleton2 = null;

    //为了保证多线程环境下正确访问,给方法加上同步锁synchronized
    //虽然线程安全了,但是每次都要同步,会影响性能
//    public static synchronized Singletion2 getInstance(){
//        if (singleton2==null){
//            singleton2 = new Singletion2();
//        }
//        return singleton2;
//    }


    //为了保证多线程环境下的另一种实现方式,双重锁检查
    //做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
   //
双重检查锁的单例模式
    public static Singletion2 getInstance(){
        if (singleton2 ==null){
            synchronized (Singletion2.class){
                if (singleton2==null){
                    singleton2 = new Singletion2();
                }
            }
        }
        return singleton2;
    }
}
方法加上同步锁synchronized调试:

当我们将其中一个线程执行并调用 getInstance()方法时,另一 个线程在调用 getInstance()方法,线程的状态由 RUNNING 变成了 MONITOR,出现阻 塞。直到第一个线程执行完,第二个线程才恢复 RUNNING 状态继续调用 getInstance() 方法

 

/**
 *线程安全问题
 */
public class Singletion2Test {
    String name = null;

    private Singletion2Test(){}

    //volatile关键字来声明单例对象
    //方便编写多线程程序和利于编译器进行优化
    private static volatile Singletion2Test  singletion2Test = null;

    public static Singletion2Test getInstance(){
        if (singletion2Test == null){
            synchronized (Singletion2Test.class){
                if (singletion2Test == null){
                    singletion2Test = new Singletion2Test();
                }
            }
        }
        return singletion2Test;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public void printInfo(){
        System.out.println(name);
    }
    public static void main(String[] args){
        Singletion2Test instance = Singletion2Test.getInstance();
        instance.setName("abc");
        Singletion2Test instance2 = Singletion2Test.getInstance();
        instance.setName("def");

        instance.printInfo();
        instance2.printInfo();
        System.out.println(instance==instance2);
    }
}

 

/**
 * 静态内部类
 * 兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题 ,完美地屏蔽了这两个缺点
 */
public class LazyInnerClassSingleton {
    //默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyInnerClassSingleton(){}
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyInnerClassSingleton getInstance(){
        //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }

    //默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
} 

饿汉式单例:

饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。

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

缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存

/**
 *饿汉式单例
 * 在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的
 */
public class Singletion4 {
    private Singletion4(){}

    //声明静态变量,在类实例化之前就初始化变量,将对象引用保存
    private static final Singletion4 SINGLETION = new Singletion4();

    public static Singletion4 getInstance(){
        return SINGLETION;
    }
}

 

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

 

 

/**
 * 饿汉式
 * 枚举式单例
 *不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
 */
import java.io.Serializable;

public enum EnumSingleton implements Serializable {
    Instance;
    public static EnumSingleton getInstance(){
        return Instance;
    }
}

  

import java.util.HashMap;
import java.util.Map;

/**
 *登记式单例
 * 内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了
 * 维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回
 */
public class Singletion6 {
    private static Map<String,Singletion6> map = new HashMap<>();

    static {
        Singletion6 singletion6 = new Singletion6();
        map.put(singletion6.getClass().getName(),singletion6);
    }
    protected Singletion6(){}

    public static Singletion6 getInstance(String name){
        if (name==null){
            name = Singletion6.class.getName();
        }
        if (map.get(name)==null){
            try {
                map.put(name, (Singletion6) Class.forName(name).newInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
}

 

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

/**
 *CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行
 *
 * 与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
 *
 * 其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,
 * 他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;
 * 每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,
 * 然后主线程就能通过await()方法,恢复执行自己的任务。
 */
public class Test {
    public static void main(String[] args){
        CountDownLatch latch = new CountDownLatch(100);
        Set<Singletion3> synchronizedSet = Collections.synchronizedSet(new HashSet<>());
        for (int a=0;a<100;a++){
            new Thread(){
                @Override
                public void run() {
                    synchronizedSet.add(Singletion3.getInstance());
                }
            }.start();
            latch.countDown();
        }

        try {
            latch.await();//等待所有线程全部完成,最终输出结果
            System.out.println(synchronizedSet.size());//1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

防止反序列化方式二(方式一请看枚举):

import java.io.Serializable;

/**
 * 序列化会通过反射调用无参数的构造方法创建一个新的对象
 */
public class SerSingleton implements Serializable {
    String name;

    public SerSingleton() {
        System.out.println("Singleton is create");
        name = "SerSingleton";
    }
    private static SerSingleton instance = new SerSingleton();
    public static SerSingleton getInstance(){
        return instance;
    }
    public static void createString(){
        System.out.println("createString in Singleton");
    }

    //java.io.ObjectInputStream.readOrdinaryObject 方法中的invokeReadResolve会通过反射的方式调用要被反序列化的类的readResolve方法
    //在反序列化后,去掉会生成多个对象实例
    private Object readResolve(){ //阻止生成新的实例,总是返回当前对象
        return instance;
    }
}

//测试

import com.mod.SerSingleton;

import java.io.*;

public class SerSingletonTest {
    public static void main(String[] args) throws Exception {
        SerSingleton s1 = null;
        SerSingleton s = SerSingleton.getInstance();
        FileOutputStream fos = new FileOutputStream("SerSingleton.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream("SerSingleton.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        s1 = (SerSingleton) ois.readObject();
        //判断是否是同一个对象
        System.out.println(s1==s);
    }
}
readResolve原理分析:

JDK 的源码->ObjectInputStream 类的 readObject()方法

 

 

 

 

在 readObject 中又调用了我们重写的 readObject0()方法。进入 readObject0() 方法,代码如下:

  case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

调用了 ObjectInputStream 的 readOrdinaryObject() 方法,我们继续进入看源码:

  readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);

通过 JDK 源码分析我们可以看出,虽然,增加 readResolve()方法返回实例,解决了单 例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两 次,只不过新创建的对象没有被返回而已。那如果,创建对象的动作发生频率增大,就 意味着内存分配开销也就随之增大

防止反射破坏单例:

  方式一:构造器判断

public class StaticSingleton{
    private StaticSingleton() {
        System.out.println("StaticSingleton is create");
        if (SingletonHolder.instance!=null){
            throw new IllegalStateException();
        }
    }
    private static class SingletonHolder{ //构造器判断,防止反射攻击
        private static StaticSingleton instance = new StaticSingleton();
    }
    public static final StaticSingleton getInstance(){
        return SingletonHolder.instance;
    }
}


import java.lang.reflect.Constructor;

/**
 * 反射测试
 */
public class SerSingletonTest2 {
    public static void main(String[] args) throws Exception {
        Class clazz = StaticSingleton.class;
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        StaticSingleton instance = (StaticSingleton) constructor.newInstance();
        StaticSingleton instance2 = StaticSingleton.getInstance();
        System.out.println(instance==instance2);
    }
}

  方式二:枚举

/**
 * 反射测试
 */
public class SerSingletonTest2 {
    public static void main(String[] args) throws Exception {
        Class clazz = EnumSingleton.class;//枚举单例
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        EnumSingleton instance = (EnumSingleton) constructor.newInstance();
        EnumSingleton instance2 = EnumSingleton.getInstance();
        System.out.println(instance==instance2);
    }
    //Exception in thread "main" java.lang.NoSuchMethodException: com.mod.EnumSingleton.<init>()
    //	at java.lang.Class.getConstructor0(Class.java:3082)
    //	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    //	at com.Test.SerSingletonTest2.main(SerSingletonTest2.java:15)
}

  

注册式单例:

注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记

在 JDK 枚举的语法特殊性,以及反射也为枚举保 驾护航,让枚举式单例成为一种比较优雅的实现

public enum EnumSingletion {
    INTERFACE;
    private Object data;
    public static EnumSingletion getInstance(){
        return INTERFACE;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

测试:

public class SerializableSingletionTest {
    public static void main(String[] args){
//        HungrySingleton s1 = null;
//        HungrySingleton s2 = HungrySingleton.getInstance();

        EnumSingletion s1 = null;
        EnumSingletion s2 = EnumSingletion.getInstance();
        s2.setData("1");

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
//            s1 = (HungrySingleton)ois.readObject();
            s1 = (EnumSingletion)ois.readObject();
            ois.close();
            System.out.println(s1.getData()); System.out.println(s2.getData()); System.out.println(s1.getData() == s2.getData());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
View Code

容器缓存的写法:

//注册式单例 -- 容器缓存的写法
public class ContainerSingleton {
    private ContainerSingleton(){}
    private static Map<String,Object> map = new ConcurrentHashMap<>();
    public static Object getBean(String className){
        if (!map.containsKey(className)){
            Object object = null;
            try {
                 object = Class.forName(className).newInstance();
                 map.put(className,object);
            }catch (Exception e){
                e.printStackTrace();
            }
            return object;
        }else {
            return map.get(className);
        }
    }
}

 

容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的

 

ThreadLocal 线程单例:

 

//ThreadLocal 线程单例
public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> instance =
            new ThreadLocal<ThreadLocalSingleton>(){
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };
    private ThreadLocalSingleton(){}
    public static ThreadLocalSingleton getInstance(){
        return instance.get();
    }
}

 

测试:

public class ExectorThread implements Runnable {
    @Override
    public void run() {
        ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+":"+singleton);
    }

}
public class ThreadLocalSingletonTest {
    public static void main(String[] args){
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());

        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("end");
        //com.vip.singleton.ThreadLocalSingleton@4b67cf4d
        //com.vip.singleton.ThreadLocalSingleton@4b67cf4d
        //com.vip.singleton.ThreadLocalSingleton@4b67cf4d
        //com.vip.singleton.ThreadLocalSingleton@4b67cf4d
        //Thread-0:com.vip.singleton.ThreadLocalSingleton@5a67a4e
        //end
        //Thread-1:com.vip.singleton.ThreadLocalSingleton@17141277
    }
}

 

ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全

ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以 空间换时间来实现线程间隔离的。

posted @ 2019-02-13 11:56  fly_bk  阅读(162)  评论(0编辑  收藏  举报