单例模式----饿汉式,懒汉式(饱汉式),双重判断模式

原创
2017/07/10 22:44
阅读数 2K

通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。

核心知识点如下:

(1) 将采用单例设计模式的类的构造方法私有化(采用private修饰)。

(2) 在其内部产生该类的实例化对象,并将其封装成private static类型。

为什么实例化对象为静态的呢?

提供访问类成员的方法,一是在类外实例化对象,用该对像对用方法,或者使用“类.方法名()”,因为构造函数为private,类外不能实例化对象,因此只能使用第二种方法。此时方法为静态方法,静态方法不能访问非静态的成员,因此类中定义的实例的实例变量也必须是静态的。

(3) 定义一个静态方法返回该类的实例。

一、什么时候使用单例模式:

当实例存在多个会引起程序逻辑错误的时候

二、好处:

       1、减少内存的占用
       2、单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
       3、因为类控制了实例化过程,所以类可以灵活更改实例化过程   

三、单例模式的三种模式

  • 1.饿汉式
package com.sunru.day0628;

/**
 * 饿汉式--就是穷,不给准备好,担心饿死。类加载就给准备好
 * 
 */
public class SingleInstance {

	// 定义一个私有的构造方法,无法在类外实例化
	private SingleInstance() {

	}

	// 有的会加final修饰符(更为严谨),添加final修饰符之后,指向的引用不能再做更改。
	// 这是final的用法:final成员变量表示常量,只能被赋值一次,赋值后值不能再改变。
	// 这句话得这么理解:
	// 对于一个final变量。
	// 如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
	// 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
	private static final SingleInstance singleInstance = new SingleInstance();

	// 静态方法返回该类的实例
	public static SingleInstance getInstance() {
		return singleInstance;
	}
}

方法一就是传说的中的饿汉模式
优点是:写起来比较简单,而且不存在多线程同步问题,避免了synchronized所造成的性能问题;
缺点是:当类SingletonTest被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。

  • 2.懒汉式(也是饱汉式)--延迟加载
package com.sunru.day0628;

/**
 * 
 * 饱汉式(懒汉式)----就是用的时候再new(线程不安全)
 * 
 */
public class SingleInstance1 {

	// 定义私有构造方法(防止通过 new SingleInstance1()去实例化)
	private SingleInstance1() {

	}

	//定义一个SingleInstance1类型的变量(不初始化,注意这里没有使用final关键字)
    //这个就不能加final,因为要在其他地方给他再次赋值呢。  
    //加了final,那就默认一直是null啦,而且还不能再次给此属性赋值。  
    //此属性是静态,那么就是共享数据,多线程并发操作共享数据是有可能的。那么就会出现下面的线程不安全现象。
	private static SingleInstance1 singleInstance1;

	// 定义一个静态的方法(调用时再初始化SingletonTest,但是多线程访问时,可能造成重复初始化问题)
	public static SingleInstance1 getInstance() {
		if (singleInstance1 == null) {
			//在这个地方,多线程的时候,  
            //可能A线程挂起,此属性还是null,那么B线程可能也判断条件OK也进来啦。  
            //然后A线程可以执行的时候就会new个对象,线程B也会new个对象。  
            //就不能保证内存的唯一性。也就是线程不安全  
			singleInstance1 = new SingleInstance1();
		}
		return singleInstance1;
	}
	///**  
    // * 为了应对上述的不安全,可以简单的如下操作给方法添加[synchronized],使之成为同步函数。  
    // * 但是:  
    // * 在很多线程的情况下,就每个线程访问都得判断锁,效率就是问题。所以,才有后面的[双重锁形式]  
    // */  
    //public static synchronized SingleInstance1 getInstance() {  
    //    if (singleInstance1 == null) {  
    //        singleInstance1 = new singleInstance1();  
    //    }  
    //    return singleInstance1;  
    //}  
}

方法二就是传说的中的饱汉模式
优点是:写起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存;使用synchronized关键字避免多线程访问时,出现多个SingletonTest实例。

缺点是:同步方法频繁调用时,效率略低。

  • 3.双重判断模式

这个模式将同步内容下放到if内部,提高了执行的效率,不必每次获取对象时都进行同步,
只有第一次才同步,创建了以后就没必要了。避免土豪模式下创建单例,可能存在的线程不安全问题。

package com.sunru.day0628;
/**
 * 双重锁形式 
 * 这个模式将同步内容下放到if内部,提高了执行的效率,不必每次获取对象时都进行同步, 
 * 只有第一次才同步,创建了以后就没必要了。避免土豪模式下创建单例,可能存在的线程不安全问题。 
 *
 */
public class SingleInstance2 {
	
	private SingleInstance2(){
		
	}
	
	private static SingleInstance2 singleInstance2;
	
	/** 
     * 静态方法同步的时候,使用的锁,就不能是this,而是类.class 
     */  
	public static SingleInstance2 getInstance(){
		
		if(singleInstance2==null){
			//这个地方可能有多个线程,在这排队,ABCD..。 
			synchronized(SingleInstance2.class){
				if(singleInstance2==null){
					//假设第一次A线程走到这,然后,呈挂起状态。这个时候,单例对象还未创建;  
                    // 假设此时,B线程也来了判断单例对象==null成立,但是,因为A线程已经给里层的if判断上锁,所以,B只能在外等着。  
                    //假设A线程被唤醒,那么,单例就会下面语句赋值,单例对象就创建啦。然后释放锁。B就可以进来啦。  
                    //B线程进来之后,先判断单例对象是否为null,发现已经不是null啦,那么就不需要创建啦。  
                    //CD线程同样,  
                    //再往后面来的,第一个if就进不来啦,那就不会判断锁了。 
					singleInstance2 = new SingleInstance2();
				}
			}
		}
		return singleInstance2;
	}

}

方法四为单例模式的最佳实现。内存占用地,效率高,线程安全,多线程操作原子性

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
1
分享
返回顶部
顶部