设计模式复习(二)-------单例模式

1.单例模式概述
  • 单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问点这个唯一实例。
  • 三个要点:
    • 某个类只能有一个实例(单例)
    • 它必须自行创建这个实例(交给这个单例内部完成)
    • 它必须自行向整个系统提供这个实例(提供唯一的全局访问点)
2.单线程代码实现:
/**
 * 	单线程单例模式
 *
 */
public class Singleton {
	
	//私有的对象实例
	private static Singleton instance = null;
	
	//私有构造函数
	private Singleton() {
		
	}		
	
	//全局访问点
	public static Singleton GetInstance() {
		//如果第一次访问,创建一个实例
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

/**
 * 测试类
 *
 */
public class test {

	public static void main(String[] args) {

		Singleton s1 = Singleton.GetInstance();
		Singleton s2 = Singleton.GetInstance();
		System.out.println(s1);
		System.out.println(s2);
		
	}
}

多线程同时创建这个实例时,会出现创建多个实例的情况,如下:

/**
 * 	单线程单例模式在多线程下出现问题
 *
 */
public class Singleton {
	
	//私有的实例
	private static Singleton instance = null;
	
	//私有构造函数
	private Singleton() {
		
	}		
	
	//全局访问点
	public static Singleton GetInstance() throws InterruptedException {
		//如果第一次访问,创建一个实例
		if(instance == null) {
            //为使效果更明显,通过判断条件后让两个睡眠3秒后,再创建实例
			Thread.sleep(3000);
			instance = new Singleton();
		}
		return instance;
	}
}
/**
 * 测试类
 *
 */
public class test {

	public static void main(String[] args) {
		Runnable r = () -> {
			Singleton getInstance = null;
			try {
				getInstance = Singleton.GetInstance();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(getInstance);
		};
        //两个实例的地址不相同,说明创建了两个实例
		Thread t1 = new Thread(r);
		Thread t2 = new Thread(r);
		t1.start();
		t2.start();
	}
}
3.多线程单例模式

如果我们让类加载的时候就创建实例,静态变量就被初始化,之后类的私有构造函数会被调用,单例类的唯一实例就被创建,这种创建方式就是饿汉式。

public class Singleton {
	
	//静态变量初始化
	private static Singleton instance = new Singleton();
	
	//私有构造函数
	private Singleton() {}		
	
	//全局访问点
	public static Singleton GetInstance() {
		return instance;
	}
}

如果我们不在单例类加载时将自己实例化,而是把实例化推迟到GetInstance()方法调用的时候创建,这就叫做懒汉式,但在高并发、多线程环境下,如果有多个线程使用多个实例对象,懒汉式加载还是有可能创建多个实例对象的。

所以我们要使用一些高级语言特有的机制(如Java、C#中的lock关键字等等)来消除创建多个实例对象的可能性。

Java多线程

我们可以在整个GetInstance()方法上加锁,这样做是正确的,但是很笨重,比如有多个线程要访问GetInstance()方法,只有一个线程能得到实例,而其他线程都要上锁等待,效率比较低。

有一种方法可以解决这个问题:双重检查锁定

/**
 * 	多线程懒加载
 *
 */
public class Singleton {
	
	//私有对象实例
	private static Singleton instance = null;
	
	//私有构造函数
	private Singleton() {}		
	
	//全局访问点
	public static Singleton GetInstance() {
        //检查实例是否存在
		if(instance == null) {
			
			//检查静态方法的类锁,互斥的访问临界区
			synchronized (Singleton.class) {
				
				if(instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

我们为什么要判断两次呢?

第一次判断:是为了判断实例是否存在,如果不是第一次创建实例,直接就返回创建好的实例,效率很好。而之前的直接对整个方法加锁,不管是否已经创建好实例,都要不停的检查类锁。

第二次判断:是为了互斥的访问临界区,防止创建多个实例。

问:饿汉式单例和懒汉式单例的异同?
答:饿汉式单例类在类被加载时就将自己实例化,优点在于无须考虑多线程同时访问的问题,可以确保实例的唯一性,调用速度和反应时间更快,但是无论系统在运行的时候是否使用该单例对象,类一经加载,单例对象就需要创建,所以系统加载时间可能会加长; 懒汉式单例类则是在第一次单例类实例化的时候,因为涉及到双重检查锁定等机制进行控制,资源初始化会耗费较长时间。

C#实现方式:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MultiThread
{
    /// <summary>
    /// 多线程单例模式示例
    /// </summary>
    public class Singleton
    {
        /// <summary>
        /// 唯一的实例对象,每次访问时重新读取instance变量的值
        /// </summary>
        private static volatile Singleton instance = null;
        /*volatile修饰:编译器在编译代码的时候会对代码的顺序进行微调,用volatile修饰保证了严格意义的顺序。
          一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
          精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。*/

        /// <summary>
        /// 辅助加锁对象--注意:Static类型
        /// </summary>
        private static readonly object lockHelper = new object();

        /// <summary>
        /// 私有构造函数,防止外部应用使用new方法创建新的实例
        /// </summary>
        private Singleton()
        {
        }

        /// <summary>
        /// 获取唯一的实例对象
        /// </summary>
        /// <returns>唯一的实例对象</returns>
        public static Singleton GetInstance()
        {
            if (instance == null) //先判断对象是否存在,不存在时再加锁处理--这样做能够保证执行效率!
            {
                lock (lockHelper) //加锁--创建临界区
                {
                    if (instance == null) //加锁后二次判断,只允许一个线程判断--有可能之前已有线程创建该对象
                    {
                        instance = new Singleton();
                    }
                }
            }

            return instance;
        }

        /// <summary>
        /// 单例类的其他接口
        /// </summary>
        public void ShowMe()
        {
            Console.WriteLine("Singleton pattern--MultiThread...");
        }

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值