本篇内容介绍了“java单例模式怎么定义”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
一、单例模式定义:
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
1、经典饿汉式:
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
特点:程序启动时加载,先加载类,再初始化静态属性,由于后面无法再对对象进行修改,从而实现线程安全,效率相对高一些。占用内存相对多一些。
缺点:如果这个类特别庞大,初始化时将会特别缓慢,还有就是如果我们用不到这个类,它仍然会创建出来,浪费了资源。
2、经典懒汉式:
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
特点:延时加载,节约了内存。效率相对低一些。利用同步块实现线程安全。
缺点:synchronized关键字是一个重锁(对象锁),它会每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了。
3、懒汉式变种—双重检查结构(不加volatile关键字修饰):
package cn.hzy.creationPattern.singleton;
public class Singleton3 {
private static Singleton3 instance = null;
private Singleton3(){
}
public static Singleton3 getInstance(){
if (instance == null) {
synchronized (instance) {
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
}
特点:属于懒汉式的变种,上面懒汉式的特点都有,但是这里优化了性能问题,没有给getInstance()方法加锁,而是只给instance = new Singleton3();加锁,也就是说只在初始化的时候会加锁,后面的访问因为instance!=null,就不会加锁。
缺点:乍一看这种模式既没有线程安全问题,又保证了单例,貌似完美了,但是JVM在创建对象的时候有可能为了优化性能而进行指令重排,
看似简单的一句 instance = new Singleton3(); JVM在创建对象的时候会有三个步骤:
1、给Singleton3分配一个内存空间
2、初始化Singleton3(也就是创建Singleton3对象)
3、将instance指向刚分配的内存空间地址
但是有可能JVM为了编译的优化提高效率就有可能变成下面一种顺序:
1、给Singleton3分配一个内存空间
2、将instance指向刚分配的内存空间地址
3、初始化Singleton3(也就是创建Singleton3对象)
其实这种情况在单线程情况下是毫无影响的,结果都一样,但是如果在多线程情况下,就有可能导致错误。
比如:A、B两个线程访问getInstance()方法,A先进入第一个if判断,然后进入synchronized块,开始初始化Singleton3,由于发生了指令重排,将instance指向刚分配的内存空间地址(此时未创建Singleton3对象),在这个时候,B访问getInstance()方法,B进入第一个if判断,因为instance已经指向了一个存在的内存空间地址,即instance!=null,此时直接返回instance(未初始化),然后再调用的时候如果A还没有初始化完毕那么就会报空指针错误。(概率很低)
解决方案:加上volatile关键字修饰,
volatile:
特性一:内存可见性,即线程A对volatile变量的修改,其他线程获取的volatile变量都是最新的。
特性二:可以禁止指令重排序。
修改如下:将 private static Singleton3 instance = null; 改为 private static volatile Singleton3 instance = null;
4、静态内部类:
package cn.hzy.creationPattern.singleton;
public class Singleton4 {
private Singleton4() {}
public static Singleton4 getInstance() {
return SingletonFactory.instance;
}
private static class SingletonFactory {
private static Singleton4 instance = new Singleton4();
}
}
特点:按特征也是属于懒汉模式,因为只会在我们需要用的时候才会创建实例对象,这里通过构造函数私有化,使用内部类来维护单例的实现,因为JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次, 并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心Singleton3出现的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。
缺点:貌似这个就完美了,但是静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去的。
5、枚举:
public enum Singleton {
INSTANCE;
public void method() {
}
}
直接调用SingleTon.INSTANCE就是单例。
特点:创建枚举默认就是线程安全的
优点:简直不要太多,1、写法简单,对比上面的实例就能发现。2、可以防止反射攻击。
针对上面的反射攻击我这里简单说一下:在上面的1、2、3、4种单例模式里面,如果不对构造函数做一些安全处理,我们可以很轻松通过反射拿到构造器并且创建不只一个实例对象,就不再是单例了。但是对于枚举,即时你通过反射拿到构造器,在创建对象实例的时候也会报错,因为枚举是可以防止反射攻击的。
怎么对构造函数做一些安全处理?
可以立一个flag,在创建一个对象实例后,改变flag的值,通过判断抛出异常。
比如:
private static boolean flag = false;
private Singleton (){
synchronized (Singleton .class) {
if(false == flag){
flag = !flag;
} else {
throw new RuntimeException("单例模式正在被反射攻击!!!");
}
}
}
通过在构造函数里面增加一个判断来保证不被反射攻击。
“java单例模式怎么定义”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。