专业编程基础技术教程

网站首页 > 基础教程 正文

设计模式:单例模式的八种姿态写法分析,你又会几种呢?

ccvgpt 2024-07-28 12:12:29 基础教程 7 ℃

推荐阅读:

单例模式的八种姿态写法分析

强烈建议:如果是没有接触单例模式的读者朋友强烈建议你们动手敲一遍,不要复制,不然没效果!

设计模式:单例模式的八种姿态写法分析,你又会几种呢?

姿态一:饿汉式1(静态变量)

package singletonPattern;
//饿汉式(静态变量)

class Singleton{
 //1、私有化构造方法,让外部不能new
 private Singleton(){

 }
 //2、本类内部创建对象实例【静态变量目的是为了类加载的时候创建实例】
 private final static Singleton instance=new Singleton();

 //3、提供一个公有的static静态方法,返回实例对象
 public static Singleton getInstance(){
 return instance;
 }
}
//以下是测试代码=====================

public class SingletenDemo1 {
 public static void main(String[] args) {
 Singleton singleton=Singleton.getInstance();
 Singleton singleton2=Singleton.getInstance();
//验证一:
 System.out.println(singleton==singleton2);
//验证二:
 System.out.println(singleton.hashCode());
 System.out.println(singleton2.hashCode());
 }
}

//运行结果:
// true
// 460141958
// 460141958

/*
饿汉式(静态变量)方法

优点:写法简单,在类加载的时候就完成了实例化,同时也就避免了线程同步问题,因此线程安全
缺点:由于是在类加载时就完成了实例化,没有达到懒加载的效果。如果一直没有使用过这个实例,就造成了内存的浪费!

总结:这种方式基于ClassLoader类加载机制避免了多线程的同步问题,只不过instance属性在类加载就实例化,在单例模式中大多数都是调用getInstance方法,
 由于getInstance方法是static静态的,调用它肯定会触发类加载!但是触发类加载的原因有很多,我们不能保证这个类会通过其他的方式触发类加载(比如调用了其他的static方法)
 这个时候初始化instance就没有达到lazy loading 懒加载的效果,可能造成内存的浪费!

 饿汉式(静态变量)这种方式可以使用但是会造成内存的浪费!

 */

姿态二:饿汉式2(static静态代码块)

package singletonPattern;
//饿汉式2(static静态代码块)

class Singleton2{
 private Singleton2(){

 }

 private static Singleton2 instance;

 static{ //把创建单例对象的操作放进了static静态代码块中==============
 instance = new Singleton2();
 }

 public static Singleton2 getInstance(){
 return instance;
 }
}
//饿汉式2(static静态代码块)其实和第一种饿汉式(静态变量)方法差不多,其优缺点一致!
//唯一不同的就是把创建单例对象的操作放进了static静态代码块中

姿态三:懒汉式1(线程不安全)

package singletonPattern;
//懒汉式1(线程不安全)
class Singleton3{
 private Singleton3(){

 }

 private static Singleton3 instance;

 public static Singleton3 getInstance(){
 if(instance == null){
 instance=new Singleton3();
 }
 return instance;
 }
}
/*
懒汉式(线程不安全)的这种方式起到了懒加载的效果,但只能在单线程下使用。
如果在多线程下,一个线程进入了if(singleton==null)判断语句块,还没执行产生实例的句子,另一个线程
又进来了,这时会产生多个实例,所以不安全。

结语:懒汉式(线程不安全)在实际开发中,不要使用这种方式!!存在潜在危险
*/

姿态四:懒汉式2(线程安全)

package singletonPattern;
//懒汉式2(线程安全)
class Singleton4{
 private Singleton4(){

 }

 private static Singleton4 instance;

 public static synchronized Singleton4 getInstance(){
 if(instance == null){
 instance=new Singleton4();
 }
 return instance;
 }
}

/*
懒汉式2(线程安全)方式

优点:线程安全
缺点:效率太低,每次调用getInstance方法都要进行同步

结语:懒汉式2(线程安全)方式在开发中不推荐使用,主要是效率太低了*/

姿态五:饿汉式2(static静态代码块)

package singletonPattern;
//懒汉式3 同步代码块(线程安全) 但是不满足单例,在多线程下依旧会有多个实例
class Singleton5{
 private Singleton5(){

 }

 private static Singleton5 instance;

 public static Singleton5 getInstance(){
 if(instance == null){ //多线程情况下可能多个线程进入这个if块
 synchronized (Singleton5.class){ //到这里只会一个一个创建实例,虽然安全,但是就不再是单例了
 instance=new Singleton5();
 }
 }
 return instance;
 }
}
/*
懒汉式3 同步代码块(线程安全) 但是不满足单例,依旧会有多个实例

结语:懒汉式3 同步代码块(线程安全)方式在开发中不使用 ,实际上这个单例设计的有点搞笑*/

姿态六:双重检查单例

package singletonPattern;
//双重检查应用实例方式
class Singleton6{
 private Singleton6(){}

 private static volatile Singleton6 singleton;

 public static Singleton6 getInstance(){
 if(singleton==null){
 synchronized(Singleton6.class){
 if(singleton == null){
 singleton= new Singleton6();
 }
 }
 }
 return singleton;
 }
}
/*
双重检查应用实例方式:

线程安全、延迟加载、效率较高

结语:开发中推荐使用!
*/

细心的童鞋会发现有一个Volatile关键字,完了,没见过,小白童鞋慌了!

Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。

这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。

姿态七:静态内部类单例

package singletonPattern;
//static静态内部类单例

class Singleton7{
 private Singleton7(){}

 private static volatile Singleton7 instance;

 //写一个static静态内部类,给该类添加一个static静态instance属性
 private static class SingletonInstance{
 private static final Singleton7 SINGLETON_7=new Singleton7();
 }

 //
 public static synchronized Singleton7 getInstence(){
 return SingletonInstance.SINGLETON_7;
 }
}
/*
静态内部类单例方式
 1、这种方式采用了类加载机制来保证初始化实例时只有一个线程
 2、巧妙的将实例化Singleton操作放进getInstance方法中,getInstance方法返回静态内部类中实例化好的Singleton
 3、类的静态属性只会在第一次加载类的时候初始化,也就是只会初始化一次,在这里,JVM帮我们保证了线程的安全,类在初始化时,别的线程无法进入。
 
 优点:线程安全、利用静态内部类特点实现延迟加载、效率高
 开发中推荐使用这种静态内部类单例方式!

static静态内部特点:
1、外部类加载不会导致内部类加载,保证了其懒加载
*/

要清楚这个单例,必须要明白static静态内部特点,也就是外部类加载不会导致内部类加载!

姿态八:饿汉式2(static静态代码块)

package singletonPattern;
//使用枚举

import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader;

enum Singleton8{
 INSTANCE;
 public void methodName(){
 System.out.println("测试数据");
 }
}
/*

枚举方式的枚举:
推荐写法,简单高效。充分利用枚举类的特性,只定义了一个实例,且枚举类是天然支持多线程的。
借助JDK1.5中添加的枚举来实现单例模式优点:
		 1、不仅能避免多线程同步问题 
		 2、还能防止反序列化重新创建新的对象

枚举方式单例是由Effective java作者Josh Bloch提倡的,结语:推荐使用!
*/

当然也可以测试一下

public class SingletonDemo8 {
 public static void main(String[] args) {
 Singleton8 instance = Singleton8.INSTANCE;
 Singleton8 instance2 = Singleton8.INSTANCE;
 System.out.println(instance==instance2);

 System.out.println(instance.hashCode());
 System.out.println(instance2.hashCode());

 instance.methodName();
 }
}

运行结果:

true
460141958
460141958
测试数据

属实没毛病!

JDK源码中单例模式的应用

先来看一段Runtime 的源码吧,并分析一下其使用的是种单例模式!

/**
 * Every Java application has a single instance of class
 * <code>Runtime</code> that allows the application to interface with
 * the environment in which the application is running. The current
 * runtime can be obtained from the <code>getRuntime</code> method.
 * <p>
 * An application cannot create its own instance of this class.
 *
 * @author unascribed
 * @see java.lang.Runtime#getRuntime()
 * @since JDK1.0
 */
public class Runtime {
 private static Runtime currentRuntime = new Runtime();

 /**
 * Returns the runtime object associated with the current Java application.
 * Most of the methods of class <code>Runtime</code> are instance
 * methods and must be invoked with respect to the current runtime object.
 *
 * @return the <code>Runtime</code> object associated with the current
 * Java application.
 */
 public static Runtime getRuntime() {
 return currentRuntime;
 }

 /** Don't let anyone else instantiate this class */
 private Runtime() {}

这应该不难看出吧!如果看不出的话只能说明你真的还没有理解单例模式,我其实想说单例模式其实是23种设计模式中最简单的一个,只是写法比较多而已!同时面试官一般都会问单例模式,它已经是很基础的了,问的稍微有点水平就是问你单例模式在JDK中哪里运用到了,显然JDK中的Runtime其实它使用的就是饿汉式单例!正如注释所说,每一个java应用程序都有一个Runtime实例。Runtime的单例模式是采用饿汉模式创建的,意思是当你加载这个类文件时,这个实例就已经存在了。

Runtime类可以取得JVM系统信息,或者使用gc()方法释放掉垃圾空间,还可以使用此类运行本机的程序。

==还有就是spring Mvc 中的controller 默认是单例模式的,解析。==

单例模式总结

  • 饿汉式(静态变量)这种方式可以使用,但是没有达到 lazy loading 懒加载的效果会造成内存的浪费!开发中不建议使用。
  • 饿汉式(static静态代码块)其实和第一种饿汉式(静态变量)方法差不多,其优缺点一致!唯一不同的就是把创建单例对象的操作放进了static静态代码块中
  • 懒汉式(线程不安全)起到了懒加载的效果,但只能在单线程下使用。在实际开发中,不要使用这种方式!!!
  • 懒汉式2(线程安全)方式线程安全但是效率太低,每次调用getInstance方法都要进行同步。所以在开发中不推荐使用。
  • 懒汉式3 同步代码块(线程安全)方式在开发中不使用 ,实际上这个设计有点搞笑哈哈。
  • 双重检查应用实例方式,线程安全、延迟加载、效率较高。因此开发中推荐使用!
  • 静态内部类单例方式线程安全、利用静态内部类特点实现延迟加载、效率高。 开发中推荐使用这种静态内部类单例方式!
  • 借助JDK1.5中添加的枚举来实现单例模式不仅能避免多线程同步问题还能防止反序列化重新创建新的对象。枚举方式单例是由Effective java作者Josh Bloch提倡的,开发中推荐使用!

单例模式必须考虑到在多线程的应用场合下的使用,毕竟现在的服务器基本上都是多核的了。


作者:宜春
原文链接:https://juejin.im/post/5ddc7a8af265da7e060656f4

Tags:

最近发表
标签列表