本篇内容介绍了“Java单例模式怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
单例模式就是将类的构造函数进行private化,然后只留出一个静态的Instance函数供外部调用者调用。
饿汉式单例,不管以后用不用这个对象,我们一开始就创建这个对象的实例,在JVM中对类的加载跟初始化,由虚拟机保证线程的安全, 需要的时候就返回已创建好的实例对象,所以比较饥饿,故此叫饿汉式单例
// 饿汉式单例 public class Hungry { // 可能会浪费空间 private byte[] data1 = new byte[1024*1024]; private byte[] data2 = new byte[1024*1024]; private byte[] data3 = new byte[1024*1024]; private byte[] data4 = new byte[1024*1024]; private Hungry(){ } private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
当我们类有太多变量的时候就不适合饿汉式创建类,在我们需要的时候才进行类初始化,这种模式叫懒汉式。比如下面的静态类初始化模式。
public class SingleInit { private SingleInit(){} // 定义一个私有类,来持有当前类的实例 private static class InstanceHolder{ public static SingleInit instance = new SingleInit(); } public static SingleInit getInstance(){ return InstanceHolder.instance; } }
三、实际的懒汉式
第一版:多线程不安全的模式,只是简单的判断是否初始化了,没初始化则初始化。
class SingletonLazy{ private static SingletonLazy instance = null; private SingletonLazy() { } /** * 此方法实现的单例,无法在多线程中使用,多线可以同时进入if方法,会导致生成多个单例对象。 * @return */ public static SingletonLazy getInstance(){ if(instance==null){ instance = new SingletonLazy(); } return instance; } }
第二版:多个线程进入getInstance的时候,进行抢锁,但是这种方法不可取,严重影响性能,每次调用都要获得锁,严重影响性能。
class SingletonLazy{ private static SingletonLazy instance = null; private SingletonLazy() { } public static synchronized SingletonLazy getInstance(){ if(instance==null){ instance = new SingletonLazy(); } return instance; } }
第三版:双重加锁机制,有两个判断
1. 调用的时候先判断是否已经非空了,如果初始化好了则直接返回。
2. 没初始化好 AB两个线程都获得类锁,A获得B没获得,然后A直接将对象创建,释放锁。但是如果此时A初始化的对象没有赋值给主内存的singleDcl,这个时候B获得锁了仍然会进行创建 如果不进行if判断,索引要加if判断同时还要将主变量设置为volatile。因为我们new 出来的对象如果对象里包含很大的数据内容肯能数据还没有创建好的哦!,所以要用volatile。
public class SingleDcl { private volatile static SingleDcl singleDcl; //保证可见性 private SingleDcl(){ } public static SingleDcl getInstance(){ if(singleDcl==null) { // 放置进入加锁代码,先判断下是否已经初始化好了 synchronized (SingleDcl.class) { // 类锁 可能会出现 AB线程都在这卡着,A获得锁,B等待获得锁。 if(singleDcl==null) { // 如果A线程初始化好了,然后通过vloatile 将变量复杂给住线程。 // 如果此时没有singleDel === null,判断 B进程 进来后还会再次执行 new 语句 singleDcl = new SingleDcl(); } } } return singleDcl; } }
破坏方法一、利用反射,强制访问类的私有构造器,去创建另一个对象
public static void main(String[] args) { // 获取类的显式构造器 Constructor<Singleton> construct = Singleton.class.getDeclaredConstructor(); // 可访问私有构造器 construct.setAccessible(true); // 利用反射构造新对象 Singleton obj1 = construct.newInstance(); // 通过正常方式获取单例对象 Singleton obj2 = Singleton.getInstance(); System.out.println(obj1 == obj2); // false }
破坏方法二、序列化与反序列化破坏单例模式
public static void main(String[] args) { // 创建输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file")); // 将单例对象写到文件中 oos.writeObject(Singleton.getInstance()); // 从文件中读取单例对象 File file = new File("Singleton.file"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Singleton newInstance = (Singleton) ois.readObject(); // 判断是否是同一个对象 System.out.println(newInstance == Singleton.getInstance()); // false }
两个对象地址不相等的原因是:readObject() 方法读入对象时它必定会返回一个新的对象实例,必然指向新的内存地址。
使用枚举实现单例模式(花样玩枚举),也是Effective Java中推荐使用的方式根据具体情况进行实例化,在初始化的时候已经给我们知道好几个实际类的类型了。它的好处:更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使面对复杂的序列和反射攻击。
public enum SingletionEnum{ SingletionEnum("单例的枚举方式"); private String str ; private SingletionEnum(String str){ this.setStr(str); } public String getStr() { return str; } public void setStr(String str) { this.str = str; } } ===== // enum 是一个什么? 本身也是一个Class类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } }
它不需要做任何额外的操作,就可以保证对象单一性与线程安全性。
当我们想通过反射破坏单例模式时候会直接报错:
// enum 是一个什么? 本身也是一个Class类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; // 获取 Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); // NoSuchMethodException: com.kuang.single.EnumSingle.<init>() System.out.println(instance1); System.out.println(instance2); } }
防止反射:枚举类默认继承了 Enum 类,在利用反射调用 newInstance() 时,会判断该类是否是一个枚举类,如果是,则抛出异常。
防止反序列化:在读入 单例对象时,每个枚举类型和枚举名字都是唯一的,所以在序列化时,仅仅只是对枚举的类型和变量名输出到文件中,在读入文件反序列化成对象时,使用 Enum 类的 valueOf(String name) 方法根据变量的名字查找对应的枚举对象。枚举类型限制主了类的时例只有那么几个。所以,在序列化和反序列化的过程中,只是写出和读入了枚举类型和名字,没有任何关于对象的操作。
“Java单例模式怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。