• 单例模式



    /**
    * 懒汉式单例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 中,为每个线程都提供一个对象,实际上是以 空间换时间来实现线程间隔离的。

  • 相关阅读:
    进制转换问题
    奶牛野炊
    BFS
    深搜DFS
    Map容器之热血格斗场
    衣服尺码
    BZOJ2789 [Poi2012]Letters
    BZOJ1098 [POI2007]办公楼biu
    BZOJ1753 [Usaco2005 qua]Who's in the Middle
    BZOJ2442 [Usaco2011 Open]修剪草坪
  • 原文地址:https://www.cnblogs.com/fly-book/p/10369046.html
Copyright © 2020-2023  润新知