设计模式-单例模式

相关阅读:

设计模式简介

设计模式-简单工厂模式

设计模式-工厂方法模式

设计模式-抽象工厂模式


今天来学习一下单例模式

单例模式

单例模式是一种常用的软件设计模式,属于对象创建型模式

单例模式保证一个类,只有一个实例存在,同时提供能对该实例加以访问的全局访问方法

所以,单例模式都是使用静态方法进行创建的(下面会进行解释)

单例模式实现过程

1.将该类的构造函数私有化,目的是禁止其他程序创建该类的对象

2.在本类中自定义一个对象,既然禁止其他程序创建该类的对象,就要自己创建一个供程序使用,否则类就没法用,更不是单例

3.提供一个可访问类自定义对象的类成员方法,对外提供该对象的访问方式

通俗的说,不能在其他地方创建该类的对象,而是通过该类提供的方法,去访问该类中已经创建好的那个对象

那么问题的关键来了,程序调用类中方法只有两种方式

①创建类的一个对象,用该对象去调用类中方法

②使用类名直接调用类中方法,格式“类名.方法名()”

上面说了,构造函数私有化后第一种情况就不能用,只能使用第二种方法,而使用类名直接调用类中方法,类中方法必须是静态的,而静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态

所以,单例模式唯一实例必须设置为静态

单例模式的几种形式:

1.懒汉式

2.饿汉式

3.双重校验锁

4.静态内部类

实例:

相关代码已上传到GitLab,地址:https://gitlab.com/louisvv/DesignPattern

下面通过实例,对单例模式几种形式进行演示

1.懒汉式

懒汉式使用了lazy加载模式,即什么时候需要使用, 再进行加载,避免浪费资源

public class LazySingleton {
    //  初始化一个对象,对象为null
    private static LazySingleton lazySingleton;

    //  将构造函数私有
    private LazySingleton() {}

    //  创建外部访问函数
    public static LazySingleton getInstance() {
        //  判断对象是否为null
        if (lazySingleton == null) {
            //  如果不为空,则创建一个LazySingleton对象
            return new LazySingleton();
        }
        return lazySingleton;
    }
}

但是,在多线程的情况下,这种方法是不安全的,为了解决多线程不安全的状况,我们对上述代码进行修改

1.1懒汉式改进版(线程安全)

修改后,我们在外部访问函数getInstance添加了synchronized锁,保证了线程的安全,但是会影响程序执行效率

public class LazySingletonSafe {
    //  初始化一个对象,对象为null
    private static LazySingletonSafe lazySingletonSafe;
    //  将构造函数私有
    private LazySingletonSafe() {}

    //  创建外部访问函数,添加synchronized锁,保证线程安全
    public static synchronized LazySingletonSafe getInstance() {
        //  判断对象是否为null
        if (lazySingletonSafe == null) {
            //  如果不为空,则创建一个LazySingleton对象
            lazySingletonSafe =new LazySingletonSafe();
            return lazySingletonSafe;
        }
        return lazySingletonSafe;
    }
}

2.饿汉式

相对于懒汉式的lazy加载模式,饿汉式采取的方式:在类加载时,就创建了对象,浪费了内存资源,但保证了线程安全

所以,相对于懒汉式,饿汉式更常用

public class Singleton {
    //  创建对象
    private static Singleton singleton=new Singleton();
    //  将构造函数私有
    private Singleton(){}
    //  创建外部访问方法
    public static Singleton getInstance(){
        return singleton;
    }
}

3.双重校验锁(JDK版本1.5+):

相对于懒汉式线程安全方式,双重校验锁的程序性能就很好,不会出现,线程等待,程序缓慢的状况

在双重校验锁下,创建对象的操作,只需要执行一次,其他线程再次进来的时候,通过第二个判断,对象已经创建成功了,直接将创建好的对象返回,无需线程等待,提高了效率

相对于懒汉式,双重校验锁的实现方式较复杂,但保证了线程安全的同时,又提高了效率,何乐而不为?

public class SingletonDoubleCheck {
    //  初始化singletonDoubleCheck对象,该对象为null
    private static SingletonDoubleCheck singletonDoubleCheck;

    //  将构造函数私有
    private SingletonDoubleCheck() {}

    //  创建外部访问函数
    public static SingletonDoubleCheck getInstance() {
        //  首先对singletonDoubleCheck进行判断,是否为空
        if (singletonDoubleCheck == null) {
            //  如果为空,使用同步锁对创建SingletonDoubleCheck过程进行修饰
            synchronized (SingletonDoubleCheck.class) {
                //  再次进行判断,singletonDoubleCheck是否为空
                if (singletonDoubleCheck == null) {
                    singletonDoubleCheck = new SingletonDoubleCheck();
                }
                return singletonDoubleCheck;
            }
        }
        return singletonDoubleCheck;
    }

4.静态内部类:

使用静态内部类的方式,能和双重校验达到同样的效果,但实现更简单

public class SingletonStatic {
    //  私有构造方法
    private SingletonStatic(){};
    //  静态内部类
    private  static class SingletonStaticHandle{
        private static final SingletonStatic singletonStatic=new SingletonStatic();
    }

    //  编写外部访问函数
    public static SingletonStatic getInstance(){
        return SingletonStaticHandle.singletonStatic;
    }

}

测试:

采用多线程的方式进行几种单例模式测试,根据打印对象哈希码值进行对比, 是否为同一个对象

1.懒汉式:

public class SingletonTest {
    public static class Mythread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println(Singleton.getInstance());
        }
    }
    public static void main(String[] args) {
        Mythread mythread1 = new Mythread();
        Mythread mythread2 = new Mythread();
        Mythread mythread3 = new Mythread();
        mythread1.start();
        mythread2.start();
        mythread3.start();
    }
}

结果如下,果然懒汉式线程不安全

Singleton.LazySingleton@7c79042f
Singleton.LazySingleton@23cebe71
Singleton.LazySingleton@3cbeafaf

1.1懒汉式改进版

public class SingletonTest {
    public static class Mythread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println(LazySingletonSafe.getInstance());
        }
    }
    public static void main(String[] args) {
        Mythread mythread1 = new Mythread();
        Mythread mythread2 = new Mythread();
        Mythread mythread3 = new Mythread();
        mythread1.start();
        mythread2.start();
        mythread3.start();
    }
}

结果,返回的都是同一个对象,线程安全

Singleton.LazySingletonSafe@1c208db1
Singleton.LazySingletonSafe@1c208db1
Singleton.LazySingletonSafe@1c208db1

2.饿汉式

public class SingletonTest {
    public static class Mythread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println(Singleton.getInstance());
        }
    }
    public static void main(String[] args) {
        Mythread mythread1 = new Mythread();
        Mythread mythread2 = new Mythread();
        Mythread mythread3 = new Mythread();
        mythread1.start();
        mythread2.start();
        mythread3.start();
    }
}

结果,饿汉式的也是线程安全的

Singleton.Singleton@8242116
Singleton.Singleton@8242116
Singleton.Singleton@8242116

3.双重校验锁:

public class SingletonTest {
    public static class Mythread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println(SingletonDoubleCheck.getInstance());
        }
    }
    public static void main(String[] args) {
        Mythread mythread1 = new Mythread();
        Mythread mythread2 = new Mythread();
        Mythread mythread3 = new Mythread();
        mythread1.start();
        mythread2.start();
        mythread3.start();
    }
}

结果:双重校验锁也ok

Singleton.SingletonDoubleCheck@488a7f84
Singleton.SingletonDoubleCheck@488a7f84
Singleton.SingletonDoubleCheck@488a7f84

4.静态内部类:

public class SingletonTest {
    public static class Mythread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println(SingletonStatic.getInstance());
        }
    }
    public static void main(String[] args) {
        Mythread mythread1 = new Mythread();
        Mythread mythread2 = new Mythread();
        Mythread mythread3 = new Mythread();
        mythread1.start();
        mythread2.start();
        mythread3.start();
    }
}

结果:ok也是线程安全的

Singleton.SingletonStatic@1c208db1
Singleton.SingletonStatic@1c208db1
Singleton.SingletonStatic@1c208db1

总结:

通过实例和测试,来对单例模式进行一下总结

所以,我们推荐使用静态内部类

优点
缺点
懒汉式
lazy加载,节省资源
线程不安全
饿汉式
线程安全,实现简单
类初始化就加载,浪费资源
双重校验式
线程安全,效率高
实现方式较复杂
静态内部类
线程安全,懒加载,实现简单,效率高
由于是静态只加载一次

应用场景:

1.Windows的任务管理器就是很典型的单例模式,你能打开两个windows任务管理器吗? 不信你自己试试看

2.应用程序的日志,一般都用单例模式实现,由于日志文件一直处于打开状态,但只能有一个实例去写日志,否则日志内容不好追加

亲,记得点下方的赞哦!

赫墨拉

我是一个喜爱大数据的小菜鸡,这里是我分享我的成长和经历的博客

You may also like...

发表评论

电子邮件地址不会被公开。