龙空技术网

Java 单例模式

囨離囨棄 117

前言:

今天姐妹们对“java中单例模式具体作用”大致比较注重,大家都需要知道一些“java中单例模式具体作用”的相关文章。那么小编也在网络上汇集了一些有关“java中单例模式具体作用””的相关知识,希望你们能喜欢,咱们快快来学习一下吧!

Java中单例(Singleton)模式是一种广泛使用的设计模式。单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。一些管理器和控制器常被设计成单例模式。

单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。

应用场景和好处

对于系统中的某些类来说,只有一个实例很重要。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

单例模式能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。

1、饿汉模式

public class Singleton{      	private static Singleton instance = new Singleton();    		private Singleton(){}    		public static Singleton newInstance(){        return instance;    }}

从代码中我们看到,类的构造函数定义为private的,保证其他类不能实例化此类,然后提供了一个静态实例并返回给调用者。饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。

优点:它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。

缺点:即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

使用场景:这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。

2、懒汉模式(非线程安全模式)

public class Singleton{    private static Singleton instance = null;    		private Singleton(){}    		public static Singleton newInstance(){        if(null == instance){            instance = new Singleton();        }        return instance;    }}

懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。

使用场景:某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。

2、懒汉模式(线程安全模式)

public class Singleton{    private static Singleton instance = null;    		private Singleton(){}    		public static synchronized Singleton newInstance(){        if(null == instance){            instance = new Singleton();        }        return instance;    }}

加锁的懒汉模式看起来即解决了线程并发问题,又实现了延迟加载,然而它存在着性能问题,依然不够完美。

性能问题:synchronized修饰的同步方法比一般方法要慢很多,如果多次调用getInstance(),累积的性能损耗就比较大了

3、双重校验锁模式

public class Singleton {    private static Singleton instance = null;    		private Singleton(){}    		public static Singleton getInstance() {      	if (instance == null) { //代码1            synchronized (Singleton.class) {                if (instance == null) {//2                    instance = new Singleton();                }            }        }        return instance;    }}

代码1:由于单例对象只需要创建一次,如果后面再次调用getInstance()只需要直接返回单例对象。因此,大部分情况下,调用getInstance()都不会执行到同步代码块,从而提高了程序性能。

代码2:假如两个线程A、B,A执行了if (instance == null)语句,它会认为单例对象没有创建,此时线程切到B也执行了同样的语句,B也认为单例对象没有创建,然后两个线程依次执行同步代码块,并分别创建了一个单例对象,因此在同步代码块中增加if (instance == null)语句。

Java中的指令重排优化定义

所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行得更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由地进行指令重排序的优化。

双重校验锁模式存在的问题

由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

4、双重校验锁模式优化

public class Singleton {    private static volatile Singleton instance = null;    private Singleton(){}    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}

通过volatile关键字优化双重校验锁模式。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了双重校验锁模式存在的问题

5、静态内部类

public class Singleton{      	private static class SingletonHolder{        public static Singleton instance = new Singleton();    }    		private Singleton(){}    		public static Singleton newInstance(){        return SingletonHolder.instance;    }}

优点:可以同时保证延迟加载和线程安全。

这种方式同样利用了类加载机制来保证只创建一个instance实例。它与饿汉模式一样,也是利用了类加载机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象的实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。

6、枚举

public enum Singleton {    INSTANCE {        @Override        protected void read() {            System.out.println("read");        }        @Override        protected void write() {            System.out.println("write");        }    };    protected abstract void read();    protected abstract void write();}

反编译后的类源码如下:

public abstract class Singleton extends Enum{    private Singleton(String s, int i)    {        super(s, i);    }    protected abstract void read();    protected abstract void write();    public static Singleton[] values()    {        Singleton asingleton[];        int i;        Singleton asingleton1[];        System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);        return asingleton1;    }    public static Singleton valueOf(String s)    {        return (Singleton)Enum.valueOf(singleton/Singleton, s);    }    Singleton(String s, int i, Singleton singleton)    {        this(s, i);    }    public static final Singleton INSTANCE;    private static final Singleton ENUM$VALUES[];    static     {        INSTANCE = new Singleton("INSTANCE", 0) {            protected void read()            {                System.out.println("read");            }            protected void write()            {                System.out.println("write");            }        };        ENUM$VALUES = (new Singleton[] {            INSTANCE        });    }}

优点:

1、线程安全

从反编译后的类源码中可以看出也是通过类加载机制保证的反编译后的源码。

2、不会因为序列化而产生新实例

不会因为序列化而产生新实例原因:枚举类自己实现了readResolve()方法,所以抗序列化,这个方法是当前类自己实现的(待验证)防止反射攻击

3、防止反射攻击

由反编译类源码可知单例类的修饰是abstract的,所以没法实例化。

标签: #java中单例模式具体作用