单例_spring的单例和单例模式的单例 - CSDN
  • 本文继续介绍23种设计模式系列之单例模式。 概念:  java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。  单例模式有以下特点:  1、单例类...

    本文继续介绍23种设计模式系列之单例模式。

    概念:
      java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。
      单例模式有以下特点:
      1、单例类只能有一个实例。
      2、单例类必须自己创建自己的唯一实例。
      3、单例类必须给所有其他对象提供这一实例。
      单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。


    一、懒汉式单例

     

    //懒汉式单例类.在第一次调用的时候实例化自己 
    public class Singleton {
        private Singleton() {}
        private static Singleton single=null;
        //静态工厂方法 
        public static Singleton getInstance() {
             if (single == null) {  
                 single = new Singleton();
             }  
            return single;
        }
    }

     

    Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

    (事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

    但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:

     

    1、在getInstance方法上加同步

     

    public static synchronized Singleton getInstance() {
             if (single == null) {  
                 single = new Singleton();
             }  
            return single;
    }

     

     

     

    2、双重检查锁定

     

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

     

    3、静态内部类

     

    public class Singleton {  
        private static class LazyHolder {  
           private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
           return LazyHolder.INSTANCE;  
        }  
    }  

    这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。

     

     

     

     

     

     

    二、饿汉式单例

     

    //饿汉式单例类.在类初始化时,已经自行实例化 
    public class Singleton1 {
        private Singleton1() {}
        private static final Singleton1 single = new Singleton1();
        //静态工厂方法 
        public static Singleton1 getInstance() {
            return single;
        }
    }

    饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

     

     

     

     

    三、登记式单例(可忽略)

    //类似Spring里面的方法,将类名注册,下次从里面直接获取。
    public class Singleton3 {
        private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
        static{
            Singleton3 single = new Singleton3();
            map.put(single.getClass().getName(), single);
        }
        //保护的默认构造子
        protected Singleton3(){}
        //静态工厂方法,返还此类惟一的实例
        public static Singleton3 getInstance(String name) {
            if(name == null) {
                name = Singleton3.class.getName();
                System.out.println("name == null"+"--->name="+name);
            }
            if(map.get(name) == null) {
                try {
                    map.put(name, (Singleton3) Class.forName(name).newInstance());
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            return map.get(name);
        }
        //一个示意性的商业方法
        public String about() {    
            return "Hello, I am RegSingleton.";    
        }    
        public static void main(String[] args) {
            Singleton3 single3 = Singleton3.getInstance(null);
            System.out.println(single3.about());
        }
    }

     登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。 

    这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。

     

    饿汉式和懒汉式区别

    从名字上来说,饿汉和懒汉,

    饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

    而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

    另外从以下两点再区分以下这两种方式:

     

    1、线程安全:

    饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

    懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。


     

    2、资源加载和性能:

    饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

    而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

    至于1、2、3这三种实现又有些区别,

    第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,

    第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

    第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

     

    什么是线程安全?

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

     

    应用

    以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:

     

    public class TestSingleton {
    	String name = null;
    
            private TestSingleton() {
    	}
    
    	private static volatile TestSingleton instance = null;
    
    	public static TestSingleton getInstance() {
               if (instance == null) {  
                 synchronized (TestSingleton.class) {  
                    if (instance == null) {  
                       instance = new TestSingleton(); 
                    }  
                 }  
               } 
               return instance;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public void printInfo() {
    		System.out.println("the name is " + name);
    	}
    
    }

    可以看到里面加了volatile关键字来声明单例对象,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加volatile呢,原因已经在下面评论中提到,

    还有疑问可参考http://www.iteye.com/topic/652440
    和http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

     

     

     

    public class TMain {
    	public static void main(String[] args){
    		TestStream ts1 = TestSingleton.getInstance();
    		ts1.setName("jason");
    		TestStream ts2 = TestSingleton.getInstance();
    		ts2.setName("0539");
    		
    		ts1.printInfo();
    		ts2.printInfo();
    		
    		if(ts1 == ts2){
    			System.out.println("创建的是同一个实例");
    		}else{
    			System.out.println("创建的不是同一个实例");
    		}
    	}
    }
    

     运行结果:

    结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。

    对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。

    更多设计模式:23种设计模式系列

    作者:jason0539

    博客:http://blog.csdn.net/jason0539(转载请说明出处)

    展开全文
  • 单例模式6种实现方式

    2018-06-04 17:21:36
    一:饿汉式public class MyObject { // 立即加载方式==饿汉模式 private static MyObject myObject = new MyObject(); private MyObject() { } public static MyObject getInstance() { ...

    一:饿汉式

    public class MyObject {
    
    	// 立即加载方式==饿汉模式
    	private static MyObject myObject = new MyObject();
    
    	private MyObject() {
    	}
    
    	public static MyObject getInstance() {
    		// 此代码版本为立即加载
    		// 此版本代码的缺点是不能有其它实例变量
    		// 因为getInstance()方法没有同步
    		// 所以有可能出现非线程安全问题
    		return myObject;
    	}
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}

    多线程获取单例,打印的hashcode是同一个值,说明对象是同一个 

    二:懒汉式(1)

    public class MyObject {
    
    	private static MyObject myObject;
    
    	private MyObject() {
    	}
    
    	// 设置同步方法效率太低了
    	// 整个方法被上锁
    	synchronized public static MyObject getInstance() {
    		try {
    			if (myObject != null) {
    			} else {
    				// 模拟在创建对象之前做一些准备性的工作
    				Thread.sleep(3000);
    				myObject = new MyObject();
    			}
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		return myObject;
    	}
    
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}


    二:懒汉式(2)


    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    
    }
    public class MyObject {
    
    	private volatile static MyObject myObject;
    
    	private MyObject() {
    	}
    
    	// 使用双检测机制来解决问题
    	// 即保证了不需要同步代码的异步
    	// 又保证了单例的效果
    	public static MyObject getInstance() {
    		try {
    			if (myObject != null) {
    			} else {
    				// 模拟在创建对象之前做一些准备性的工作
    				Thread.sleep(3000);
    				synchronized (MyObject.class) {
    					if (myObject == null) {
    						myObject = new MyObject();
    					}
    				}
    			}
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		return myObject;
    	}
    	// 此版本的代码称为:
    	// 双重检查Double-Check Locking
    
    }
    public static void main(String[] args) {
    		
    		
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}

    三:使用静态内置类实现单例模式

    public class MyObject {
    
    	// 内部类方式
    	private static class MyObjectHandler {
    		private static MyObject myObject = new MyObject();
    	}
    
    	private MyObject() {
    	}
    
    	public static MyObject getInstance() {
    		return MyObjectHandler.myObject;
    	}
    
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}

    四:使用静态代码块实现单例模式

    public class MyObject {
    
    	private static MyObject instance = null;
    
    	private MyObject() {
    	}
    
    	static {
    		instance = new MyObject();
    	}
    
    	public static MyObject getInstance() {
    		return instance;
    	}
    
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 5; i++) {
    			System.out.println(MyObject.getInstance().hashCode());
    		}
    	}
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}

    五:使用枚举实现单例模式

    public enum MyObject {
    	connectionFactory;
    
    	private Connection connection;
    
    	private MyObject() {
    		try {
    			System.out.println("调用了MyObject的构造");
    			String url = "jdbc:sqlserver://localhost:1079;databaseName=ghydb";
    			String username = "sa";
    			String password = "";
    			String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
    			Class.forName(driverName);
    			connection = DriverManager.getConnection(url, username, password);
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public Connection getConnection() {
    		return connection;
    	}
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 5; i++) {
    			System.out.println(MyObject.connectionFactory.getConnection()
    					.hashCode());
    		}
    	}
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}


    六:序列化与反序列化的单例模式实现

    public class MyObject implements Serializable {
    
    	private static final long serialVersionUID = 888L;
    
    	// 内部类方式
    	private static class MyObjectHandler {
    		private static final MyObject myObject = new MyObject();
    	}
    
    	private MyObject() {
    	}
    
    	public static MyObject getInstance() {
    		return MyObjectHandler.myObject;
    	}
    
    	protected Object readResolve() throws ObjectStreamException {
    		System.out.println("调用了readResolve方法!");
    		return MyObjectHandler.myObject;
    	}
    
    }
    public class SaveAndRead {
    
    	public static void main(String[] args) {
    		try {
    			MyObject myObject = MyObject.getInstance();
    			
    			FileOutputStream fosRef = new FileOutputStream(new File(
    					"myObjectFile.txt"));
    			ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
    			oosRef.writeObject(myObject);
    			oosRef.close();
    			fosRef.close();
    			System.out.println(myObject.hashCode());
    		} catch (FileNotFoundException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    
    		try {
    			FileInputStream fisRef = new FileInputStream(new File(
    					"myObjectFile.txt"));
    			ObjectInputStream iosRef = new ObjectInputStream(fisRef);
    			MyObject myObject = (MyObject) iosRef.readObject();
    			iosRef.close();
    			fisRef.close();
    			System.out.println(myObject.hashCode());
    		} catch (FileNotFoundException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    
    	}
    
    }




    展开全文
  • 设计模式之单例

    2018-04-07 15:30:48
    单例 单例模式的使用场景 举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中...

    单例

    单例模式的使用场景

    举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。

    继续说回收站,我们在实际使用中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。

    再举一个例子,网站的计数器,一般也是采用单例模式实现,如果你存在多个计数器,每一个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题。同样多线程的线程池的设计一般也是采用单例模式,这是由于线程池需要方便对池中的线程进行控制

    同样,对于一些应用程序的日志应用,或者web开发中读取配置文件都适合使用单例模式,如HttpApplication 就是单例的典型应用。

    从上述的例子中我们可以总结出适合使用单例模式的场景和优缺点:

    场景

    1.封装一些常用的工具类,保证整个应用常用的数据统一
    2.方便资源相互通信的环境,保存一些共享数据在内存中,其他类随时可以读取。
    3.需要生成唯一序列的环境

    优点:

    1、提供了对唯一实例的受控访问。
    2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    3、允许可变数目的实例。

    缺点:

    1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
    3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

    展开全文
  • java单例模式

    2020-03-09 15:47:46
     本文首先概述了单例模式产生动机,揭示了单例模式的本质和应用场景。紧接着,我们给出了单例模式在单线程环境下的两种经典实现:饿汉式和懒汉式,但是饿汉式是线程安全的,而懒汉式是非线程安全的。在多线程环境下...

    摘要: 
       
      本文首先概述了单例模式产生动机,揭示了单例模式的本质和应用场景。紧接着,我们给出了单例模式在单线程环境下的两种经典实现:饿汉式 和懒汉式,但是饿汉式是线程安全的,而懒汉式是非线程安全的。在多线程环境下,我们特别介绍了五种方式来在多线程环境下创建线程安全的单例,即分别使用synchronized方法synchronized块静态内部类双重检查模式 和ThreadLocal 来实现懒汉式单例,并总结出实现效率高且线程安全的懒汉式单例所需要注意的事项。


    一. 单例模式概述

      单例模式(Singleton),也叫单子模式,是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。

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

      综上所述,单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。


    二. 单例模式及其单线程环境下的经典实现

      单例模式应该是23种设计模式中最简单的一种模式了,下面我们从单例模式的定义、类型、结构和使用要素四个方面来介绍它。

    1、单例模式理论基础

    定义: 确保一个类只有一个实例,并为整个系统提供一个全局访问点 (向整个系统提供这个实例)。

    类型: 创建型模式

    结构:

                          单例模式类图.gif-9.2kB

      特别地,为了更好地理解上面的类图,我们以此为契机,介绍一下类图的几个知识点:

    • 类图分为三部分,依次是类名、属性、方法;
    • 以<<开头和以>>结尾的为注释信息;
    • 修饰符+代表public,-代表private,#代表protected,什么都没有代表包可见;
    • 带下划线的属性或方法代表是静态的。

    三要素:

    • 私有的构造方法;

    • 指向自己实例的私有静态引用;

    • 以自己实例为返回值的静态的公有方法。


    2、单线程环境下的两种经典实现

      在介绍单线程环境中单例模式的两种经典实现之前,我们有必要先解释一下 立即加载 和延迟加载 两个概念。

    • 立即加载 : 在类加载初始化的时候就主动创建实例;

    • 延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。

        在单线程环境下,单例模式根据实例化对象时机的不同,有两种经典的实现:一种是 饿汉式单例(立即加载),一种是 懒汉式单例(延迟加载)饿汉式单例在单例类被加载时候,就实例化一个对象并交给自己的引用;而懒汉式单例只有在真正使用的时候才会实例化一个对象并交给自己的引用。代码示例分别如下:


    饿汉式单例:

    // 饿汉式单例
    public class Singleton1 {
    
        // 指向自己实例的私有静态引用,主动创建
        private static Singleton1 singleton1 = new Singleton1();
    
        // 私有的构造方法
        private Singleton1(){}
    
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static Singleton1 getSingleton1(){
            return singleton1;
        }
    }
    • 我们知道,类加载的方式是按需加载,且加载一次。。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

    懒汉式单例:

    // 懒汉式单例
    public class Singleton2 {
    
        // 指向自己实例的私有静态引用
        private static Singleton2 singleton2;
    
        // 私有的构造方法
        private Singleton2(){}
    
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static Singleton2 getSingleton2(){
            // 被动创建,在真正需要使用时才去创建
            if (singleton2 == null) {
                singleton2 = new Singleton2();
            }
            return singleton2;
        }
    }
    •  我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。

      总之,从速度和反应时间角度来讲,饿汉式(又称立即加载)要好一些;从资源利用效率上说,懒汉式(又称延迟加载)要好一些。


    3、单例模式的优点

      我们从单例模式的定义和实现,可以知道单例模式具有以下几个优点:

    • 在内存中只有一个对象,节省内存空间;

    • 避免频繁的创建销毁对象,可以提高性能;

    • 避免对共享资源的多重占用,简化访问;

    • 为整个系统提供一个全局访问点。


    4、单例模式的使用场景

      由于单例模式具有以上优点,并且形式上比较简单,所以是日常开发中用的比较多的一种设计模式,其核心在于为整个系统提供一个唯一的实例,其应用场景包括但不仅限于以下几种:

    • 有状态的工具类对象;
    • 频繁访问数据库或文件的对象;

    5、单例模式的注意事项

      在使用单例模式时,我们必须使用单例类提供的公有工厂方法得到单例对象,而不应该使用反射来创建,否则将会实例化一个新对象。此外,在多线程环境下使用单例模式时,应特别注意线程安全问题,我在下文会重点讲到这一点。


    三. 多线程环境下单例模式的实现

      在单线程环境下,无论是饿汉式单例还是懒汉式单例,它们都能够正常工作。但是,在多线程环境下,情形就发生了变化:由于饿汉式单例天生就是线程安全的,可以直接用于多线程而不会出现问题;但懒汉式单例本身是非线程安全的,因此就会出现多个实例的情况,与单例模式的初衷是相背离的。下面我重点阐述以下几个问题:

    • 为什么说饿汉式单例天生就是线程安全的?

    • 传统的懒汉式单例为什么是非线程安全的?

    • 怎么修改传统的懒汉式单例,使其线程变得安全?

    • 线程安全的单例的实现还有哪些,怎么实现?

    • 双重检查模式、Volatile关键字 在单例模式中的应用

    • ThreadLocal 在单例模式中的应用


      特别地,为了能够更好的观察到单例模式的实现是否是线程安全的,我们提供了一个简单的测试程序来验证。该示例程序的判断原理是:

      开启多个线程来分别获取单例,然后打印它们所获取到的单例的hashCode值。若它们获取的单例是相同的(该单例模式的实现是线程安全的),那么它们的hashCode值一定完全一致;若它们的hashCode值不完全一致,那么获取的单例必定不是同一个,即该单例模式的实现不是线程安全的,是多例的。注意,相应输出结果附在每个单例模式实现示例后。

    public class Test {
        public static void main(String[] args) {
            Thread[] threads = new Thread[10];
            for (int i = 0; i < threads.length; i++) {
                threads[i] = new TestThread();
            }
    
            for (int i = 0; i < threads.length; i++) {
                threads[i].start();
            }
        }
    
    }
    
    class TestThread extends Thread {
        @Override
        public void run() {
            // 对于不同单例模式的实现,只需更改相应的单例类名及其公有静态工厂方法名即可
            int hash = Singleton5.getSingleton5().hashCode();  
            System.out.println(hash);
        }
    }

    1、为什么说饿汉式单例天生就是线程安全的?

    // 饿汉式单例
    public class Singleton1 {
    
        // 指向自己实例的私有静态引用,主动创建
        private static Singleton1 singleton1 = new Singleton1();
    
        // 私有的构造方法
        private Singleton1(){}
    
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static Singleton1 getSingleton1(){
            return singleton1;
        }
    }/* Output(完全一致): 
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
     *///:~

      我们已经在上面提到,类加载的方式是按需加载,且只加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说,饿汉式单例天生就是线程安全的。


    2、传统的懒汉式单例为什么是非线程安全的?

    // 传统懒汉式单例
    public class Singleton2 {
    
        // 指向自己实例的私有静态引用
        private static Singleton2 singleton2;
    
        // 私有的构造方法
        private Singleton2(){}
    
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static Singleton2 getSingleton2(){
            // 被动创建,在真正需要使用时才去创建
            if (singleton2 == null) {
                singleton2 = new Singleton2();
            }
            return singleton2;
        }
    }/* Output(不完全一致): 
            1084284121
            2136955031
            2136955031
            1104499981
            298825033
            298825033
            2136955031
            482535999
            298825033
            2136955031
     *///:~

      上面发生非线程安全的一个显著原因是,会有多个线程同时进入 if (singleton2 == null) {…} 语句块的情形发生。当这种这种情形发生后,该单例类就会创建出多个实例,违背单例模式的初衷。因此,传统的懒汉式单例是非线程安全的。


    3、实现线程安全的懒汉式单例的几种正确姿势

    1)、同步延迟加载 — synchronized方法

    // 线程安全的懒汉式单例
    public class Singleton2 {
    
        private static Singleton2 singleton2;
    
        private Singleton2(){}
    
        // 使用 synchronized 修饰,临界资源的同步互斥访问
        public static synchronized Singleton2 getSingleton2(){
            if (singleton2 == null) {
                singleton2 = new Singleton2();
            }
            return singleton2;
        }
    }/* Output(完全一致): 
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
     *///:~

      该实现与上面传统懒汉式单例的实现唯一的差别就在于:是否使用 synchronized 修饰 getSingleton2()方法。若使用,就保证了对临界资源的同步互斥访问,也就保证了单例。

      从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗。同步方法效率低,那我们考虑使用同步代码块来实现。


    2)、同步延迟加载 — synchronized块

    // 线程安全的懒汉式单例
    public class Singleton2 {
    
        private static Singleton2 singleton2;
    
        private Singleton2(){}
    
    
        public static Singleton2 getSingleton2(){
            synchronized(Singleton2.class){  // 使用 synchronized 块,临界资源的同步互斥访问
                if (singleton2 == null) { 
                    singleton2 = new Singleton2();
                }
            }
            return singleton2;
        }
    }/* Output(完全一致): 
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
            16993205
     *///:~

      该实现与上面synchronized方法版本实现类似,此不赘述。从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率仍然比较低,事实上,和使用synchronized方法的版本相比,基本没有任何效率上的提高。


    3)、同步延迟加载 — 使用内部类实现延迟加载

    // 线程安全的懒汉式单例
    public class Singleton5 {
    
        // 私有内部类,按需加载,用时加载,也就是延迟加载
        private static class Holder {
            private static Singleton5 singleton5 = new Singleton5();
        }
    
        private Singleton5() {
    
        }
    
        public static Singleton5 getSingleton5() {
            return Holder.singleton5;
        }
    }
    /* Output(完全一致): 
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
            482535999
     *///:~
    • 如上述代码所示,我们可以使用内部类实现线程安全的懒汉式单例,这种方式也是一种效率比较高的做法。至于其为什么是线程安全的,其与问题 “为什么说饿汉式单例天生就是线程安全的?” 相类似,此不赘述。

    四. 单例模式与双重检查(Double-Check idiom)

      使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率。对应的代码清单如下:

    // 线程安全的懒汉式单例
    public class Singleton3 {
    
        //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
        private static volatile Singleton3 singleton3;
    
        private Singleton3() {
        }
    
        public static Singleton3 getSingleton3() {
            // Double-Check idiom
            if (singleton3 == null) {
                synchronized (Singleton3.class) {       // 1
                    // 只需在第一次创建实例时才同步
                    if (singleton3 == null) {       // 2
                        singleton3 = new Singleton3();      // 3
                    }
                }
            }
            return singleton3;
        }
    }/* Output(完全一致): 
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
            1104499981
     *///:~
    •  

      如上述代码所示,为了在保证单例的前提下提高运行效率,我们需要对 singleton3 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同步获取锁了)。这种做法无疑是优秀的,但是我们必须注意一点:
       
      必须使用volatile关键字修饰单例引用。


      那么,如果上述的实现没有使用 volatile 修饰 singleton3,会导致什么情形发生呢? 为解释该问题,我们分两步来阐述:

    (1)、当我们写了 new 操作,JVM 到底会发生什么?

      首先,我们要明白的是: new Singleton3() 是一个非原子操作。代码行singleton3 = new Singleton3(); 的执行过程可以形象地用如下3行伪代码来表示:

    memory = allocate();        //1:分配对象的内存空间
    ctorInstance(memory);       //2:初始化对象
    singleton3 = memory;        //3:使singleton3指向刚分配的内存地址
    •  

      但实际上,这个过程可能发生无序写入(指令重排序),也就是说上面的3行指令可能会被重排序导致先执行第3行后执行第2行,也就是说其真实执行顺序可能是下面这种:

    memory = allocate();        //1:分配对象的内存空间
    singleton3 = memory;        //3:使singleton3指向刚分配的内存地址
    ctorInstance(memory);       //2:初始化对象
    •  

      这段伪代码演示的情况不仅是可能的,而且是一些 JIT 编译器上真实发生的现象。


    (2)、重排序情景再现 
       
      了解 new 操作是非原子的并且可能发生重排序这一事实后,我们回过头看使用 Double-Check idiom 的同步延迟加载的实现:

      我们需要重新考察上述清单中的 //3 行。此行代码创建了一个 Singleton 对象并初始化变量 singleton3 来引用此对象。这行代码存在的问题是,在 Singleton 构造函数体执行之前,变量 singleton3 可能提前成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程将得到的是一个不完整(未初始化)的对象,会导致系统崩溃。下面是程序可能的一组执行步骤:

      1、线程 1 进入 getSingleton3() 方法; 
      2、由于 singleton3 为 null,线程 1 在 //1 处进入 synchronized 块; 
      3、同样由于 singleton3 为 null,线程 1 直接前进到 //3 处,但在构造函数执行之前,使实例成为非 null,并且该实例是未初始化的; 
      4、线程 1 被线程 2 预占; 
      5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 得到一个不完整(未初始化)的 Singleton 对象; 
      6、线程 2 被线程 1 预占。 
      7、线程 1 通过运行 Singleton3 对象的构造函数来完成对该对象的初始化。

      显然,一旦我们的程序在执行过程中发生了上述情形,就会造成灾难性的后果,而这种安全隐患正是由于指令重排序的问题所导致的。让人兴奋地是,volatile 关键字正好可以完美解决了这个问题。也就是说,我们只需使用volatile关键字修饰单例引用就可以避免上述灾难。


    五. 单例模式 与 ThreadLocal

      借助于 ThreadLocal,我们可以实现双重检查模式的变体。我们将临界资源线程局部化,具体到本例就是将双重检测的第一层检测条件 if (instance == null) 转换为 线程局部范围内的操作 。这里的 ThreadLocal 也只是用作标识而已,用来标识每个线程是否已访问过:如果访问过,则不再需要走同步块,这样就提高了一定的效率。对应的代码清单如下:

    // 线程安全的懒汉式单例
    public class Singleton4 {
    
        // ThreadLocal 线程局部变量
        private static ThreadLocal<Singleton4> threadLocal = new ThreadLocal<Singleton4>();
        private static Singleton4 singleton4 = null;
    
        private Singleton4(){}
    
        public static Singleton4 getSingleton4(){
            if (threadLocal.get() == null) {        // 第一次检查:该线程是否第一次访问
                createSingleton4();
            }
            return singleton4;
        }
    
        public static void createSingleton4(){
            synchronized (Singleton4.class) {
                if (singleton4 == null) {          // 第二次检查:该单例是否被创建
                    singleton4 = new Singleton4();   // 只执行一次
                }
            }
            threadLocal.set(singleton4);      // 将单例放入当前线程的局部变量中 
        }
    }/* Output(完全一致): 
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
            1028355155
    *///:~
    •  

      借助于 ThreadLocal,我们也可以实现线程安全的懒汉式单例。但与直接双重检查模式使用,使用ThreadLocal的实现在效率上还不如双重检查锁定。


    六. 小结

      本文首先介绍了单例模式的定义和结构,并给出了其在单线程和多线程环境下的几种经典实现。特别地,我们知道,传统的饿汉式单例无论在单线程还是多线程环境下都是线程安全的,但是传统的懒汉式单例在多线程环境下是非线程安全的。为此,我们特别介绍了五种方式来在多线程环境下创建线程安全的单例,包括:

    • 使用synchronized方法实现懒汉式单例;

    • 使用synchronized块实现懒汉式单例;

    • 使用静态内部类实现懒汉式单例;

    • 使用双重检查模式实现懒汉式单例;

    • 使用ThreadLocal实现懒汉式单例;


      当然,实现懒汉式单例还有其他方式。但是,这五种是比较经典的实现,也是我们应该掌握的几种实现方式。从这五种实现中,我们可以总结出,要想实现效率高的线程安全的单例,我们必须注意以下两点:

    • 尽量减少同步块的作用域;

    • 尽量使用细粒度的锁。

    参考资料

    Java 中的双重检查(Double-Check)
    单例模式与双重检测
    用happen-before规则重新审视DCL
    JAVA设计模式之单例模式
    23种设计模式(1):单例模式

    本文转自:书呆子Rico 的博客http://blog.csdn.net/justloveyou_/article/details/64127789

    展开全文
  • 初遇设计模式在上个寒假,当时把每个设计模式过了一遍,对设计模式有了一个最初级的了解。这个学期借了几本设计模式的书籍看,听了老师的设计模式课,对设计模式算是有个更进一步的认识。后面可能会不定期更新一下...
  • 单例

    2020-08-02 17:30:38
    单例模式 每一种设计模式都是为了解决特定的问题,单例模式从名字就可以看出,是软件系统中只需要一个对象时使用。 如果一个类在系统中只能有一个实例,可以通过如下代码实现 <?php /** * Class Singleton ...
  • 单例和多例的区别

    2018-08-07 08:56:00
    单例多例需要搞明白两个问题: 1. 什么是单例多例; 2. 如何产生单例多例;...所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求...
  • 单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保...
  • 单例模式

    2020-08-03 14:27:53
    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不...
  • 因进程需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计。 二. 单例模式的特点 1、单例模式只能有一个实例。 2、单例类必须创建自己的唯一实例。 3、单例类...
  • 单例模式的使用总结

    2020-04-21 11:20:12
    一、单例模式的定义和应用场景 (一)定义及基本要点 (二)应用场景 二、饿汉式单例模式 (一)基本代码展示分析 (二)基本分析和建议 三、懒汉式单例模式(双重检查锁) (一)基本代码展示分析 (二)...
  • 本文主要介绍java的单例模式,以及详细剖析静态内部类之所以能够实现单例的原理。OK,废话不多说,进入正文。 首先我们要先了解下单例的四大原则: 1.构造私有。 2.以静态方法或者枚举返回实例。 3.确保实例只有...
  • C++中的单例模式

    2013-09-17 17:18:14
    单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出,...
  • 单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。一些管理器和控制器常被设计成单例模式。  单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约...
  • 单例模式优缺点

    2012-12-03 09:50:28
    2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。 3、允许可变数目的实例。   主要缺点: 1、由于单利模式中没有抽象层,因此...
  • 单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计...
  • 单例模式,参考完整代码在GitHub 地址:https://github.com/zhang-xiaoxiang/patter23 或者https://github.com/zhang-xiaoxiang/DesignPatterns23 一般三步走,1初始化2构造器私有3提供获取实例的方法 1单例模式---...
  • struts2的action,servlet和springmvc的单例多例问题
  • Java实现单例的5种方式 1. 什么是单例模式 单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,...
  • 单例模式的五种写法

    2019-06-23 13:52:48
    其中接下来我们要写的是单例模式,属于创建型模式。 单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。下面...
1 2 3 4 5 ... 20
收藏数 312,113
精华内容 124,845
关键字:

单例