栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA设计模式之单例模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA设计模式之单例模式

  1. 什么是单例模式:
    单例模式是指在内存中只会创建且仅创建一次对象的设计模式
  2. 单例模式特点:
    (1)单例类只能有一个实例。
    (2)单例类必须自己创建自己的唯一实例。
    (3)单例类必须给所有其他对象提供这一实例。
饿汉式单例
public class Hungry {
    private final static Hungry HUNGRY = new Hungry();

	//构造器私有,其他人就无法new一个对象,保证内存中只有一个对象
    private Hungry() {
    }

	//提供实例的方法,static:保证可见性
    public static Hungry getHungry() {
        return HUNGRY;
    }
}

在类创建的时候就已经实例化,不会改变,所以线程安全。但是,如下

public class Hungry {
    private final static Hungry HUNGRY = new Hungry();

    private byte[] bytes = new byte[1024 * 1024];
    private byte[] bytes2 = new byte[1024 * 1024];
    private byte[] bytes3 = new byte[1024 * 1024];
    private byte[] bytes4 = new byte[1024 * 1024];
    private byte[] bytes5 = new byte[1024 * 1024];

    private Hungry() {
    }

    public static Hungry getHungry() {
        return HUNGRY;
    }
}

可能会浪费空间。

懒汉式单例
public class LazyMan {
    private LazyMan() {

    }

    private static LazyMan LAYMAN = null;

	//用的时候创建
    public static LazyMan getLazyMan() {
        if (LAYMAN == null)
            LAYMAN = new LazyMan();
        return LAYMAN;
    }
}

但是,它是线程不安全的
测试:

public class LazyMan {
    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "创建");
    }

    private static LazyMan LAYMAN = null;

    public static LazyMan getLazyMan() {
        if (LAYMAN == null)
            LAYMAN = new LazyMan();
        return LAYMAN;
    }

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

控制台:

Thread-2创建
Thread-1创建
Thread-0创建

解决办法:加锁
双重检查锁模式

public class LazyMan {
    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "创建");
    }

    private static LazyMan LAYMAN = null;

	//双重检测锁模式的 懒汉式单例 DCL懒汉式
    public static LazyMan getLazyMan() {
        if (LAYMAN == null) {
            synchronized (LazyMan.class) {
                if (LAYMAN == null) {
                    LAYMAN = new LazyMan();
                }
            }
        }
        return LAYMAN;
    }

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

问题解决。
但是,解析LAYMAN = new LazyMan();,它不是一个原子性的操作,包括以下三部分。1:分配内存空间 2:执行构造方法,初始化对象 3:把这个对象指向这个空间 我们期望是1 2 3 顺序执行,但在指令重排后CPU不一定按1 2 3 顺序执行,有可能 1 3 2。当线程A执行 1 3 2 顺序的2时,线程B进入,因为这个对象已经指向这个空间所以线程B判断出LAYMAN非空,执行了返回语句,但是实际上LAYMAN并未初始化,所以会出现错误。
解决办法:
volatile :防止指令重排

public class LazyMan {
    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "创建");
    }

    private volatile static LazyMan LAYMAN = null;

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

}

问题解决。
道高一尺魔高一丈,反射破坏

import java.lang.reflect.Constructor;


public class LazyMan {
    private LazyMan() {
        //System.out.println(Thread.currentThread().getName() + "创建");
    }

    private volatile static LazyMan LAYMAN = null;

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


    public static void main(String[] args) throws Exception {
        LazyMan lazyMan = LazyMan.getLazyMan();
        Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan1);
        System.out.println(lazyMan2);

    }
}

控制台:

cn.smxy.ln.LazyMan@119d7047
cn.smxy.ln.LazyMan@776ec8df
cn.smxy.ln.LazyMan@4eec7777

解决办法:
加锁

import java.lang.reflect.Constructor;


public class LazyMan {
    private LazyMan() {
        //System.out.println(Thread.currentThread().getName() + "创建");
        synchronized (LazyMan.class) {
            if (LAYMAN != null) {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan LAYMAN = null;

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


    public static void main(String[] args) throws Exception {
        LazyMan lazyMan = LazyMan.getLazyMan();
        Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan1);
        System.out.println(lazyMan2);

    }
}

问题解决。
但是,如果不创建lazyMan 依然有问题 如下:

import java.lang.reflect.Constructor;


public class LazyMan {
    private LazyMan() {
        //System.out.println(Thread.currentThread().getName() + "创建");
        synchronized (LazyMan.class) {
            if (LAYMAN != null) {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan LAYMAN = null;

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


    public static void main(String[] args) throws Exception {
        //LazyMan lazyMan = LazyMan.getLazyMan();
        Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        //System.out.println(lazyMan);
        System.out.println(lazyMan1);
        System.out.println(lazyMan2);

    }
}

控制台:

cn.smxy.ln.LazyMan@119d7047
cn.smxy.ln.LazyMan@776ec8df

单例模式又被破坏

解决办法:
添加标志位

import java.lang.reflect.Constructor;


public class LazyMan {

    private static boolean xnn = false;

    private LazyMan() {
        //System.out.println(Thread.currentThread().getName() + "创建");
        synchronized (LazyMan.class) {
            if (xnn == false) {
                xnn = true;
            } else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan LAYMAN = null;

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


    public static void main(String[] args) throws Exception {
        //LazyMan lazyMan = LazyMan.getLazyMan();
        Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        //System.out.println(lazyMan);
        System.out.println(lazyMan1);
        System.out.println(lazyMan2);

    }
}

问题解决。
实际上Java反射机制仍能破坏单例模式。
但是枚举Enum可以防止反射和反序列化时破坏单例。

实例:

public enum EnumSingle {
    INSTANCE;

    public EnumSingle getIntance() {
        return INSTANCE;
    }
}

总结

(1)单例模式常见的写法有两种:懒汉式,饿汉式

(2)懒汉式:在类初始化时,已经自行实例化

(3)饿汉式:在第一次调用的时候实例化自己

(4)在开发中如果对内存要求非常高,那么使用懒汉式写法,可以在特定时候才创建该对象;

(5)如果对内存要求不高使用饿汉式写法,因为简单不易出错,且没有任何并发安全和性能问题

(6)在单例对象上添加volatile关键字可以防止指令重排序

(7)最优雅的实现方式是使用枚举,其代码精简,没有线程安全问题,且 Enum 类内部防止反射和反序列化时破坏单例。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/865448.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号