返回顶部
扩大
缩小

Heaton

单例模式(四)

 

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

 

介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应用实例: 1、一个党只能有一个主席。 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

应用场景: 保证一个类仅有一个实例, 并提供一个访问它的全局访问点。
Spring 中的单例模式完成了后半句话, 即提供了全局的访问点 BeanFactory。 但没有从构造器级别去
控制单例, 这是因为 
Spring 管理的是是任意的 Java 对象。 Spring 下默认的 Bean 均为单例。

归类 特点 穷举
创建型模式 保证从系统启动到系统终止, 全过程只会产生一个实
例。
当我们在应用中遇到功能性冲突的时候, 需要使用单
例模式。
配置文件、 日历、 IOC 容器


常用单例模式写法: 饿汉式、 懒汉式、 注册式、 序列化。
 

单例模式:初衷就是为了使资源能够共享,只需要赋值或者初始化一次,大家都能够重复利用。

 

饿汉式:绝对的线程安全,在线程还没出现以前就已经实例化了,不可能存在访问安全问题。

在实例使用之前,不管你用不用,我都先new出来再说,避免了线程安全问题。

package com.tzy.Singleton;

/**
 * @author Heaton
 * @date 2018/3/29 0029 21:26
 * @describe 饿汉式单例
 */
public class Hungry {
//它是在类加载的时候就立即初始化,并且创建单例对象
    //优点:没有加任何的锁、执行效率比较高,
    //在用户体验上来说,比懒汉式更好

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

    private Hungry() {
    }

    private static final Hungry hungry = new Hungry();

    public static Hungry getInstance() {
        return hungry;
    }


}

懒汉式:默认加载的时候不实例化,在需要用到这个实例的时候才实例化,延时加载。

package Singleton;

/**
 * @author Heaton
 * @date 2018/3/29 0029 21:54
 * @describe 懒汉式单例 线程不安全
 */
public class LazyOne {
    private LazyOne() {
    }

    //静态块,公共内存区域
    private static LazyOne lazy = null;

    public static LazyOne getInstance() {
        //调用方法之前,先判断
        //如果没有初始化,将其进行初始化,并且赋值
        //将该实例缓存好
        if (lazy == null) {
            lazy = new LazyOne();
        }
        return lazy;
    }


}
package com.tzy.Singleton;


import java.util.concurrent.CountDownLatch;

/**
* @author Heaton
* @date 2018/3/29 0029 23:04
* @describe 线程安全测试
*/
public class ThreadSafeTest {
    public static void main(String[] args) {
        int count = 200;
        //发令枪,我就能想到运动员
        CountDownLatch latch = new CountDownLatch(count);
        long start = System.currentTimeMillis();
        for (int i = 0; i < count;i ++) {
            new Thread("线程"+i){
                @Override
                public void run() {
                    try{
                        try {
                            // 阻塞
                            // count = 0 就会释放所有的共享锁
                            // 万箭齐发
                            latch.await();
                        }catch(Exception e){
                            e.printStackTrace();
                        }

                        //必然会调用,可能会有很多线程同时去访问getInstance()
                        Object obj = LazyOne.getInstance();
                        System.out.println(this.getName()+"--"+System.currentTimeMillis() + ":" + obj);

                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }.start(); //每循环一次,就启动一个线程,具有一定的随机性

            //每次启动一个线程,count --
            latch.countDown();

        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));



    }

}

发现问题,出现了不同的对象,单例就失败了,证明线程不安全,所以就有锁机制出来

package com.tzy.Singleton;

/**
* @author Heaton
* @date 2018/3/29 0029 23:23
* @describe 懒汉--二加锁
*/
public class LazyTwo {

    private LazyTwo(){}

    private static LazyTwo lazy = null;

    public static synchronized LazyTwo getInstance(){

        if(lazy == null){
            lazy = new LazyTwo();
        }
        return lazy;

    }

}
package com.tzy.Singleton;



/**
* @author Heaton
* @date 2018/3/29 0029 23:26
* @describe 性能测试
*/
public class LazyTest {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 200000000;i ++) {
            Object obj = LazyOne.getInstance();
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));

        long start1 = System.currentTimeMillis();
        for (int i = 0; i < 200000000;i ++) {
            Object obj = LazyTwo.getInstance();
        }
        long end1 = System.currentTimeMillis();
        System.out.println("总耗时:" + (end1 - start1));
    }

}

发现加锁好慢啊。。。。(线程倒是安全了)怎么办好纠结!!!

见证奇迹的时刻到了------

package com.tzy.Singleton;

/**
* @author Heaton
* @date 2018/3/29 0029 23:28
* @describe 超级饿汉666
*/
//特点:在外部类被调用的时候内部类才会被加载
    //内部类一定是要在方法调用之前初始化
    //巧妙地避免了线程安全问题

    //这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
    //完美地屏蔽了这两个缺点
    //史上最牛B的单例模式的实现方式
public class LazyThree {

    private static boolean initialized = false;

    //默认使用LazyThree的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的

    private LazyThree(){

        synchronized (LazyThree.class){
            if(initialized == false){
                initialized = !initialized;
            }else{
                throw new RuntimeException("单例已被侵犯");
            }
        }

    }


    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //final 保证这个方法不会被重写,重载
    public static final LazyThree getInstance(){
        //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
        //相当于第一次返回时发现内部类没加载就加载内部类,
        //内部类里有共享的不可变的内部单例,而第二次调用时,
        //返现有内部类,故而直接调用,实现单例,提高性能
    }


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


}
package com.tzy.Singleton;


import java.lang.reflect.Constructor;

/**
* @author Heaton
* @date 2018/3/29 0029 23:39
* @describe 超级饿汉反射测试
*/
public class LazyThreeTest {

    public static void main(String[] args) {

        try{
            Class<?> clazz = LazyThree.class;

            //通过反射拿到私有的构造方法
            Constructor c = clazz.getDeclaredConstructor(null);
            //强制访问
            c.setAccessible(true);

            //暴力初始化
            Object o1 = c.newInstance();

            //调用了两次构造方法,相当于new了两次
            //犯了原则性问题,
            Object o2 = c.newInstance();

            System.out.println(o1 == o2);
//            Object o2 = c.newInstance();

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

锁住了反射调用,提供了安全的反射操作,并且性能是杠杠的哦

package Singleton;



/**
* @author Heaton
* @date 2018/3/29 0029 23:26
* @describe 性能测试
*/
public class LazyTest {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 200000000;i ++) {
            Object obj = LazyOne.getInstance();
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));

        long start1 = System.currentTimeMillis();
        for (int i = 0; i < 200000000;i ++) {
            Object obj1 = LazyTwo.getInstance();
        }
        long end1 = System.currentTimeMillis();
        System.out.println("总耗时:" + (end1 - start1));

        long start2 = System.currentTimeMillis();
        for (int i = 0; i < 200000000;i ++) {
            Object obj2 = LazyThree.getInstance();
        }
        long end2 = System.currentTimeMillis();
        System.out.println("总耗时:" + (end2 - start2));
    }

}

注册登记式:每使用一次,都往一个固定的容器中去注册并且将使用过的对象进行缓存,下次去

取对象的时候,就直接从缓存中取值,以保证每次获取的都是同一个对象,(IOC的单例就是这样)

package com.tzy.Singleton;

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

/**
* @author Heaton
* @date 2018/3/30 0030 13:48
* @describe 注册试单例
*/

//Spring中的做法,就是用这种注册式单例
public class BeanFactory {

    private BeanFactory(){}

    //线程安全
    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();

    public static synchronized Object getBean(String className){

        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);
        }

    }


}
package com.tzy.Singleton;

/**
* @author Heaton
* @date 2018/3/30 0030 13:51
* @describe spring提供一个对象,然后单例创建他
*/
public class Pojo {
}
package com.tzy.Singleton;

import java.util.concurrent.CountDownLatch;

/**
* @author Heaton
* @date 2018/3/30 0030 13:49
* @describe 注册式单例测试
*/
public class BeanFactoryTest {
    public static void main(String[] args) {

        int count = 200;

        //发令枪,我就能想到运动员
        CountDownLatch latch = new CountDownLatch(count);

        long start = System.currentTimeMillis();
        for (int i = 0; i < count;i ++) {
            new Thread(){
                @Override
                public void run() {
                    try{

                        try {
                            // 阻塞
                            // count = 0 就会释放所有的共享锁
                            // 万箭齐发
                            latch.await();
                        }catch(Exception e){
                            e.printStackTrace();
                        }

                        //必然会调用,可能会有很多线程同时去访问getInstance()
                        Object obj = BeanFactory.getBean("com.tzy.Singleton.Pojo");
                        System.out.println(System.currentTimeMillis() + ":" + obj);

                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }.start(); //每循环一次,就启动一个线程,具有一定的随机性

            //每次启动一个线程,count --
            latch.countDown();

        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));


    }
}

序列化和反序列化保证单例:重写readResolve()

package com.tzy.Singleton;

import java.io.Serializable;

/**
* @author Heaton
* @date 2018/3/30 0030 14:16
* @describe 序列化式单例
 *            业务主要解决需要修改的配置文件等信息,
 *            如果配置文件修改,主机宕机,那么内存中的配置文件就会丢失。
 *            就需要读取到硬盘等储存上
*/

//反序列化时导致单例破坏
public class Seriable implements Serializable {
    /*
    一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象.
    不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则.
    单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来.
     */

    //序列化就是说把内存中的状态通过转换成字节码的形式
    //从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
    //内存中状态给永久保存下来了

    //反序列化
    //讲已经持久化的字节码内容,转换为IO流
    //通过IO流的读取,进而将读取的内容转换为Java对象
    //在转换过程中会重新创建对象new


    public  final static Seriable INSTANCE = new Seriable();
    private Seriable(){}

    public static  Seriable getInstance(){
        return INSTANCE;
    }

    /*private  Object readResolve(){
        return  INSTANCE;
    }*/

}
package com.tzy.Singleton;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * Created by Tom on 2018/3/8.
 */
public class SeriableTest {
    public static void main(String[] args) {

        Seriable s1 = null;
        Seriable s2 = Seriable.getInstance();

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


            FileInputStream fis = new FileInputStream("Seriable.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (Seriable)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

发现对象是不同的,在反序列化的时候。

所以我们可以重写readResolve()方法。

那么结果就会变为

readResolve()

 

总结一下:

单例模式就是个用户提供一个 独特且唯一的 实例。

饿汉式:上来就给用户一个实例,管你要不要,那么就会对用户产生负担(没有使用,内存消耗),像一个人买东西,你就给他

            购物车,袋子等等,他不一定会用到购物车啊。(好处就是线程安全,线程没有启用的时候,对象就有了)

懒汉式:比如用户买东西,你会问他需要什么,如果需要购物车,就看有没有,如果有就给他用,用完了归还,没有就给他

            造一个,用完了在归还。(那么如果同时来了2个用户,就需要造2个,相当于2个线程同时访问),会产生线程问题,

            有可能会造出2个,那么就破坏了单例,这时就需要给程序对象上锁。那么就会存在有一个线程等待的问题,会造成

            执行效率低下。

有没有可以解决不消耗内存的单例模式呢

加强版单例模式(饿汉加强)

我们知道静态内部类只有在外部类加载的时候才会创建,我们可以给静态内部类里,加入一个可以共用的内存空间,来储存

我们想要的这个单例。只有在用户需要的时候,我们在调用一个方法来加载我们的饿汉,饿汉被加载的时候会去加载静态内

部类,如果没有用户调用,那么静态内部类是不会被加载的,节约了内存。并且线程安全。

这样做的同时还有一个问题需要我们考虑,如果饿汉在创建的时候就加载了2个饿汉呢?也会存在不安全。这时我们可以锁住

反射对象,告诉程序,你创建2个就是不行,对饿汉的创建加入锁的机制,这样避免饿汉被反射创建两次,就可以达到线程安

全,使程序只是在创建饿汉上执行锁机制,一旦有了饿汉,那么程序运行时不在调用时访问锁,及达到线程安全,又达到效率

提高,而且不会产生空消耗。

IOC其实就是解决了内存消耗问题的map容器

 

 

 

posted on 2018-05-11 22:46  咘雷扎克  阅读(132)  评论(0编辑  收藏  举报

导航