设计模型-五种单例模型

一. 什么是单例模式

只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计。

单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。

单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;

能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。

二. 单例模式的特点

1. 单例模式只能有一个实例。

2. 单例类必须创建自己的唯一实例。

3. 单例类必须向其他对象提供这一实例。

三. 单例模式VS静态类

在知道了什么是单例模式后,我想你一定会想到静态类,“既然只使用一个对象,为何不干脆使用静态类?”,这里我会将单例模式和静态类进行一个比较。

1. 单例可以继承和被继承,方法可以被override,而静态方法不可以。

2. 静态方法中产生的对象会在执行后被释放,进而被GC清理,不会一直存在于内存中。

3. 静态类会在第一次运行时初始化,单例模式可以有其他的选择,即可以延迟加载。

4. 基于2, 3条,由于单例对象往往存在于DAO层(例如sessionFactory),如果反复的初始化和释放,则会占用很多资源,而使用单例模式将其常驻于内存可以更加节约资源。

5. 静态方法有更高的访问效率。

6. 单例模式很容易被测试。

几个关于静态类的误解:

误解一:静态方法常驻内存而实例方法不是。

实际上,特殊编写的实例方法可以常驻内存,而静态方法需要不断初始化和释放。

误解二:静态方法在堆(heap)上,实例方法在栈(stack)上。

实际上,都是加载到特殊的不可写的代码内存区域中。

静态类和单例模式情景的选择:

情景一:不需要维持任何状态,仅仅用于全局访问,此时更适合使用静态类。

情景二:需要维持一些特定的状态,此时更适合使用单例模式。

以上转自:https://my.oschina.net/u/4261956/blog/3252045

单例模型的写法

实现单例方式有五种:饿汉式、懒汉式、双重校验锁(DCL)、静态内部类、枚举。

饿汉式

优点:在类加载的时候就完成实例化,避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

public class Singleton1 {
    private static final Singleton1 INSTANCE = new Singleton1();
    
    private Singleton1() {
        System.out.println("饿汉式,可用");
    }
    
    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

懒汉式

优点:达到了懒加载的效果。

缺点:线程不安全。

  1. 对于普通懒汉式,线程不安全。

    原因:在进入if (instance == null) 时,可能一个线程进入后就切换到了另一个线程,而此时并未创建实例对象,这个线程又再次进入了if代码块。

    public class Singleton2 {
        private static Singleton2 instance;
    
        private Singleton2() {
            System.out.println("懒汉式,线程不安全,多线程不可用");
        }
    
        public static Singleton2 getInstance() {
            if (instance == null) {
                instance = new Singleton2();
            }
            return instance;
        }
    }
    
  2. 对方法添加synchronized保证线程安全。

    synchronized保证了getInstance()线程安全,但对方法进行同步效率不高。

    public class Singleton3 {
        private static Singleton3 instance;
    
        private Singleton3() {
            System.out.println("懒汉式(synchronized方法),效率太低");
        }
    
        public static synchronized Singleton3 getInstance() {
            if (instance == null) {
                instance = new Singleton3();
            }
            return instance;
        }
    }
    
  3. 改进方案2,使用synchronized代码块。

    使用了synchronized代码块改进,但又出现了和方案1一样的线程安全问题。

    public class Singleton4 {
        private static Singleton4 instance;
    
        private Singleton4() {
            System.out.println("懒汉式(synchronized代码块),线程不安全,多线程不可用");
        }
    
        public static Singleton4 getInstance() {
            if (instance == null) {
                synchronized (Singleton4.class) {
                    // 此处若有线程阻塞,其它线程就仍可以进入到了前面if
                    instance = new Singleton4();
                }
            }
            return instance;
        }
    }
    

双重校验锁(DCL)

针对上一个改进,使用两个if (instance == null) 

这里先别管实现的Serializable接口和readResolve()方法,这两个用于后面的序列化测试。

另外,此处的volatile声明是很重要的,volatile变量有两种特性。

一是保证了此变量对所有线程的可见性。“可见性”指的是当一条线程修改了这个变量的值,新值对于其它线程来说是可以立即知道的。

二是禁止指令重排序优化。但volatile变量的运行在并发编程下并非是安全的,因为不能保证原子性。详细请自己去看相关资料。

import java.io.Serializable;

public class Singleton5 implements Serializable {
    private static final long serialVersionUID = 1L;
    private static volatile Singleton5 instance;

    private Singleton5() {
        System.out.println("懒汉式优化(Double Check Lock)双重校验锁,推荐用");
    }

    public static Singleton5 getInstance() {
        if (instance == null) {
            // 未被初始化,但是无法确定这时其他线程是否已经对其初始化,因此添加对象锁进行互斥
            synchronized (Singleton5.class) {
                // 再一次进行检查,因为有可能在当前线程阻塞的时候,其他线程对instance进行初始化
                if (instance == null) {
                    // 此时还未被初始化的话,在这里初始化可以保证线程安全
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }

    // 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致
    private Object readResolve() {
        return getInstance();
    }
}

静态内部类

静态内部类方式在类被加载时并不会立即实例化。

而是在需要实例化时,调用getInstance()方法,才会装载内部类,从而完成对象实例化。

import java.io.Serializable;

public class Singleton6 implements Serializable {
    private static final long serialVersionUID = 1L;

    private Singleton6() {
        System.out.println("饿汉式优化(静态内部类),推荐用");
    }

    private static class SingletonInstance {
        private static final Singleton6 INSTANCE = new Singleton6();
    }

    public static Singleton6 getInstance() {
        return SingletonInstance.INSTANCE;
    }

    // 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致
    private Object readResolve() {
        return getInstance();
    }
}

枚举

枚举方法不仅保证了线程安全问题,还提供了序列化机制。

public enum Singleton7 {
    INSTANCE;

    private Singleton7() {
        System.out.println("枚举,最好方法");
    }

    public static Singleton7 getInstance() {
        return INSTANCE;
    }
}

测试

线程安全测试

public class TestSafety {
    static Singleton1 s1_a;
    static Singleton1 s1_b;
    static Singleton2 s2_a;
    static Singleton2 s2_b;
    static Singleton3 s3_a;
    static Singleton3 s3_b;
    static Singleton4 s4_a;
    static Singleton4 s4_b;
    static Singleton5 s5_a;
    static Singleton5 s5_b;
    static Singleton6 s6_a;
    static Singleton6 s6_b;
    static Singleton7 s7_a;
    static Singleton7 s7_b;

    public static void getS1_A() {
        s1_a = Singleton1.getInstance();
    }

    public static void getS1_B() {
        s1_b = Singleton1.getInstance();
    }

    public static void getS2_A() {
        s2_a = Singleton2.getInstance();
    }

    public static void getS2_B() {
        s2_b = Singleton2.getInstance();
    }

    public static void getS3_A() {
        s3_a = Singleton3.getInstance();
    }

    public static void getS3_B() {
        s3_b = Singleton3.getInstance();
    }

    public static void getS4_A() {
        s4_a = Singleton4.getInstance();
    }

    public static void getS4_B() {
        s4_b = Singleton4.getInstance();
    }

    public static void getS5_A() {
        s5_a = Singleton5.getInstance();
    }

    public static void getS5_B() {
        s5_b = Singleton5.getInstance();
    }

    public static void getS6_A() {
        s6_a = Singleton6.getInstance();
    }

    public static void getS6_B() {
        s6_b = Singleton6.getInstance();
    }

    public static void getS7_A() {
        s7_a = Singleton7.getInstance();
    }

    public static void getS7_B() {
        s7_b = Singleton7.getInstance();
    }

    public static void main(String[] args) {
        Thread t1_a = new Thread(TestSafety::getS1_A);
        Thread t1_b = new Thread(TestSafety::getS1_B);
        t1_a.start();
        t1_b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("s1_a == s1_b ? " + (s1_a == s1_b));
        System.out.println();

        Thread t2_a = new Thread(TestSafety::getS2_A);
        Thread t2_b = new Thread(TestSafety::getS2_B);
        t2_a.start();
        t2_b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("s2_a == s2_b ? " + (s2_a == s2_b));
        System.out.println();

        Thread t3_a = new Thread(TestSafety::getS3_A);
        Thread t3_b = new Thread(TestSafety::getS3_B);
        t3_a.start();
        t3_b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("s3_a == s3_b ? " + (s3_a == s3_b));
        System.out.println();

        Thread t4_a = new Thread(TestSafety::getS4_A);
        Thread t4_b = new Thread(TestSafety::getS4_B);
        t4_a.start();
        t4_b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("s4_a == s4_b ? " + (s4_a == s4_b));
        System.out.println();

        Thread t5_a = new Thread(TestSafety::getS5_A);
        Thread t5_b = new Thread(TestSafety::getS5_B);
        t5_a.start();
        t5_b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("s5_a == s5_b ? " + (s5_a == s5_b));
        System.out.println();

        Thread t6_a = new Thread(TestSafety::getS6_A);
        Thread t6_b = new Thread(TestSafety::getS6_B);
        t6_a.start();
        t6_b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("s6_a == s6_b ? " + (s6_a == s6_b));
        System.out.println();

        Thread t7_a = new Thread(TestSafety::getS7_A);
        Thread t7_b = new Thread(TestSafety::getS7_B);
        t7_a.start();
        t7_b.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("s7_a == s7_b ? " + (s7_a == s7_b));
        System.out.println();
    }
}

输出结果:

饿汉式,可用
s1_a == s1_b ? true

懒汉式,线程不安全,多线程不可用
懒汉式,线程不安全,多线程不可用
s2_a == s2_b ? false

懒汉式(synchronized方法),效率太低
s3_a == s3_b ? true

懒汉式(synchronized代码块),线程不安全,多线程不可用
懒汉式(synchronized代码块),线程不安全,多线程不可用
s4_a == s4_b ? false

懒汉式优化(Double Check Lock)双重校验锁,推荐用
s5_a == s5_b ? true

饿汉式优化(静态内部类),推荐用
s6_a == s6_b ? true

枚举,最好方法
s7_a == s7_b ? true

序列化测试

只对双重校验锁、静态内部类、枚举测试。

默认只有枚举能保证序列化后对象仍相等,其它需要加readResolve()方法才能。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;

public class TestSerializable {
    // 测试序列化后的对象是否相等
    public static void main(String[] args) {
        Singleton5 s5_a = Singleton5.getInstance();
        Singleton5 s5_b = Singleton5.getInstance();
        System.out.println("序列化前:s5_a == s5_b ? " + (s5_a == s5_b));
        Path file5 = Paths.get("object5.txt");
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream(file5.toFile()))) {
            out.writeObject(s5_a);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream(file5.toFile()))) {
            s5_b = (Singleton5) in.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("序列化后:s5_a == s5_b ? " + (s5_a == s5_b));
        System.out.println();

        Singleton6 s6_a = Singleton6.getInstance();
        Singleton6 s6_b = Singleton6.getInstance();
        System.out.println("序列化前:s6_a == s6_b ? " + (s6_a == s6_b));
        Path file6 = Paths.get("object6.txt");
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream(file6.toFile()))) {
            out.writeObject(s6_a);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream(file6.toFile()))) {
            s6_b = (Singleton6) in.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("序列化后:s6_a == s6_b ? " + (s6_a == s6_b));
        System.out.println();

        Singleton7 s7_a = Singleton7.getInstance();
        Singleton7 s7_b = Singleton7.getInstance();
        System.out.println("序列化前:s7_a == s7_b ? " + (s7_a == s7_b));
        Path file7 = Paths.get("object7.txt");
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream(file7.toFile()))) {
            out.writeObject(s7_a);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream(file7.toFile()))) {
            s7_b = (Singleton7) in.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("序列化后:s7_a == s7_b ? " + (s7_a == s7_b));
        System.out.println();
    }
}

先把readResolve()方法注释掉;输出结果为:

懒汉式优化(Double Check Lock)双重校验锁,推荐用
序列化前:s5_a == s5_b ? true
序列化后:s5_a == s5_b ? false

饿汉式优化(静态内部类),推荐用
序列化前:s6_a == s6_b ? true
序列化后:s6_a == s6_b ? false

枚举,最好方法
序列化前:s7_a == s7_b ? true
序列化后:s7_a == s7_b ? true

加上readResolve()方法,枚举不需要,枚举甚至连Serializable接口都不需实现;输出结果如下:

懒汉式优化(Double Check Lock)双重校验锁,推荐用
序列化前:s5_a == s5_b ? true
序列化后:s5_a == s5_b ? true

饿汉式优化(静态内部类),推荐用
序列化前:s6_a == s6_b ? true
序列化后:s6_a == s6_b ? true

枚举,最好方法
序列化前:s7_a == s7_b ? true
序列化后:s7_a == s7_b ? true

使用场景

  • 文件管理系统
  • 日志记录类
  • 与数据库的连接

应用实例

java.lang.Runtime#getRuntime()

以上转自:https://www.cnblogs.com/qiu_jiaqi/p/Design-Patterns-Singleton.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值