设计模式-单例模式
相关阅读:
今天来学习一下单例模式
单例模式
单例模式是一种常用的软件设计模式,属于对象创建型模式
单例模式保证一个类,只有一个实例存在,同时提供能对该实例加以访问的全局访问方法
所以,单例模式都是使用静态方法进行创建的(下面会进行解释)
单例模式实现过程:
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.应用程序的日志,一般都用单例模式实现,由于日志文件一直处于打开状态,但只能有一个实例去写日志,否则日志内容不好追加
亲,记得点下方的赞哦!