java的单例模式

什么是单例模式(Singleton) 设计模式的一种 一个类只有一个实例对象

 

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

 

为什么要使用单例模式

有些类频繁创建和销毁消耗资源, 可以完全复用,比如IO处理,数据库操作,全局用一个类就好了.

所以我们将这个类设置成单例类,全局只有一个,一次创建就可重复使用.

 

 

单例模式的写法

1.饿汉式 静态常量

2.饿汉式 静态代码块

3.懒汉式

4.懒汉式 + synchronized

5.懒汉式 + 双重检锁

6.懒汉式 + 双重检锁 + volatile

7.静态内部类

8.枚举

 

写法1.饿汉式 静态常量

public class HungryMan {

    private HungryMan(){}

    private static HungryMan hungryMan = new HungryMan();

    public static HungryMan getInstance(){
        return hungryMan;
    }
}

写法2.饿汉式 静态代码块

public class HungryMan {

    private static HungryMan hungryMan;

    static {
        hungryMan = new HungryMan();
    }

    private  HungryMan(){}

    public static HungryMan getInstance() {
        return hungryMan;
    }
}

 说明下

懒汉式:指全局的单例实例在第一次被使用时构建。

饿汉式:指全局的单例实例在类装载时构建。

从它们的区别也能看出来,日常我们使用的较多的应该是懒汉式的单例,毕竟按需加载才能做到资源的最大化利用

写法3.懒汉式

public class LazyMan {

    private LazyMan(){}

    private static LazyMan lazyMan;

    public static  LazyMan getInstance(){
        if (lazyMan == null) {
           lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

说明下

但是懒汉式是线程不安全的,多个线程访问会出现问题

/**
 * @description: 懒汉式 单线程可用
 */
public class LazyMan {

    private LazyMan(){
        System.out.println("运行线程 = " + Thread.currentThread().getName());
    }

    private static LazyMan lazyMan;

    public static  LazyMan getInstance(){
        if (lazyMan == null) {
           lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

/***
 * 模拟多线程访问单例类   懒汉式线程不安全   因为多线程情况下 会构建多个对象
 */
class Test{
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

运行结果如下

 说明下 解决写法3 问题 使用写法4

4.懒汉式 + synchronized

/**
 * @description:懒汉式 + synchronized
 */
public class LazyMan {

    private LazyMan() {
        System.out.println("运行线程 = " + Thread.currentThread().getName());
    }

    private static LazyMan lazyMan;

    public static synchronized LazyMan getInstance() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

运行结果如下

说明下 写法4 性能低 不推荐使用  解决使用写法5

写法5.懒汉式 + 双重检锁

 

/**
 * @description: 懒汉式 + 双重检锁 
 */
public class LazyMan {

    private LazyMan() {
        System.out.println("运行线程 = " + Thread.currentThread().getName());
    }

    private static LazyMan lazyMan;

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

class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
               LazyMan.getInstance();
            }).start();
        }
    }
}

运行结果如下

 说明下

 new LazyMan(); 不是原子性操作

需要完成以下几步 1 2 3

    1.分配空间

    2.执行构造方法,初始化构造

    3.对象指向内存地址

  JVM虚拟机为了执行效率可能对以上操作进行指令重排 原来的123可能编程132

  多线程情况下 还是不安全的  解决使用写法6

写法6.懒汉式 + 双重检锁 + volatile

/**
 * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰  避免指令重排
 */
public class LazyMan {

    private LazyMan() {
        System.out.println("运行线程 = " + Thread.currentThread().getName());
    }

    private volatile static LazyMan lazyMan;

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

class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
               LazyMan.getInstance();
            }).start();
        }
    }
}

运行结果如下

 

 

写法7.使用静态内部类

静态内部类实现单例
/**
 * @description: 静态内部类
 */
public class Holder {
    private Holder (){}

    public static Holder getInstance(){
        return innerClass.holder;
    }

    public static class innerClass{
        private static final Holder holder = new Holder();
    }
}

 说明下

还有一个问题就是反射可以破坏单例

情况一     一个使用getInstance获得单例对象  一个通过反射获得对象

/**
 * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰
 */
public class LazyMan {

    private LazyMan() {
        System.out.println("运行线程 = " + Thread.currentThread().getName());
    }

    private volatile static LazyMan lazyMan;

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

class TestReflect {
    /***
     * 使用反射会破坏单例
     * @param args
     */
    public static void main(String[] args) throws Exception {
        LazyMan instance = LazyMan.getInstance();
        //使用反射来获取单例对象
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan instance2 = constructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);
    }
}

运行结果如下

 

 

 说明下 

通过反射破坏了单例  创建了两个对象

解决加锁 手动抛出异常

public class LazyMan {

    private LazyMan() {
        synchronized (LazyMan.class){
            if (lazyMan != null) {
                throw new RuntimeException("禁止使用反射破坏单例");
            }
        }
    }

    private volatile static LazyMan lazyMan;

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

class TestReflect {
    /***
     * 使用反射会破坏单例
     * @param args
     */
    public static void main(String[] args) throws Exception {
        LazyMan instance = LazyMan.getInstance();
        //使用反射来获取单例对象
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan instance2 = constructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);
    }
}

运行结果如下

 

 

情况二     直接通过反射创建两个单例对象

/**
 * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰 + synchronized
 */
public class LazyMan {

    private LazyMan() {
        synchronized (LazyMan.class){
            if (lazyMan != null) {
                throw new RuntimeException("禁止使用反射破坏单例");
            }
        }
    }

    private volatile static LazyMan lazyMan;

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

class TestReflect {
    /***
     * 使用反射会破坏单例
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //直接通过反射创建两个单例对象
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan instance2 = constructor.newInstance();
        LazyMan instance3= constructor.newInstance();
        System.out.println(instance2);
        System.out.println(instance3);
    }
}

运行结果如下

 

 说明下

还是出现了两个对象破坏了单例

解决设置标志位

/**
 * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰 + synchronized +标志位
 */
public class LazyMan {

    //设置标志位
    private static boolean flag = false;
    private LazyMan() {
        synchronized (LazyMan.class){
            if (flag == false){
                flag = rue;
            }else {
                throw new RuntimeException("禁止使用反射破坏单例");
            }
        }
    }

    private volatile static LazyMan lazyMan;

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

class TestReflect {
    /***
     * 使用反射会破坏单例
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //直接通过反射创建两个单例对象
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan instance2 = constructor.newInstance();
        LazyMan instance3= constructor.newInstance();
        System.out.println(instance2);
        System.out.println(instance3);
    }
}

运行结果如下

 情况三   直接通过反射创建两个单例对象  破坏标志位

/**
 *
 * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰
 */
public class LazyMan {

    //设置标志位
    private static boolean flag = false;
    private LazyMan() {
        synchronized (LazyMan.class){
            if (flag == false){
                flag = true;
            }else {
                throw new RuntimeException("禁止使用反射破坏单例");
            }
        }
    }

    private volatile static LazyMan lazyMan;

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

class TestReflect {
    /***
     * 使用反射会破坏单例  破坏标志位
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //破坏标志位
        Field flag = LazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);

        //直接通过反射创建两个单例对象
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan instance2 = constructor.newInstance();

        flag.set(instance2,false);

        LazyMan instance3= constructor.newInstance();
        System.out.println(instance2);
        System.out.println(instance3);
    }
}

运行结果如下

 

 说明下

通过反射 破坏标志位 仍然能破坏单例

 

分析

在Constructor.getInstance 方法中 的这段代码说明反射不能破坏枚举的 引出写法8解决

 

 

写法8.枚举

/**
 * 使用枚举
 */
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance() {
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) {
        EnumSingle instance = EnumSingle.INSTANCE;
        EnumSingle instance2 = EnumSingle.INSTANCE;
        System.out.println(instance);
        System.out.println(instance2);

    }
}

运行结果如下

 

 

  //验证反射不能破坏枚举   

Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);

constructor.setAccessible(true);

EnumSingle enumSingle = constructor.newInstance();

EnumSingle enumSingle2 = constructor.newInstance();

 

运行结果如下 和在Constructor.getInstance 方法中 抛出的异常一样

 说明下

所有枚举都继承Enum  

它的构造方法有两个参数

 

posted @ 2021-03-13 18:09  wf.zhang  阅读(86)  评论(0编辑  收藏  举报