设计模式之单例模式

单例模式几乎是所有模式中最简单的,用的也挺多,今天就讲讲单例模式

单例模式的定义:

保证一个类仅有一个实例(在内存中仅有该类一份字节码),并提供一个访问它的全局访问方法即可,.实例就是说运行期间才有的

使用场景:比如一个类中的方法在很多类中都要使用到,有静态和非静态的方法,非静态的方法必须使用对象才能调用,

那就意味着在运行期间就会new很多类在内存中,这样会导致内存吃紧,因此需要优化,优化就是减少内存的空间,而内存是存储对象的,所以减少对象的个数就是优化了内存,因此只需要在内存中保存一份这个类的对象即可,


既然在外部类中不能new这个对象,那么这个类的构造函数一定要私有,这个单例类只能在本类中创建了,那么它必须要对外提供访问到这个对象的方法,(也就是说单例类要对外提供方法访问到这个对象的实例)所以单例的2个条件就出来了

1:构造函数要私有化,也就是创建对象权限只能在本类中使用

2:提供方法让外界能够拿到单例类的对象实例


下面就按照上面分析的写一个单例的demo

<span style="font-size:24px;">package cn.handcool.singlton;
public class SingleTon {
	private SingleTon(){}
	public static SingleTon getInstance(){
		return null;
	}
}</span>

上面的代码 getInstance()方法返回的是null对象,这肯定是不对的,必须要返回SingleTon类的实例对象,那么创建对象可以在一开始就创建,还有一种就是在运行期间创建,这就是所谓单例模式的懒汉式和饿汉式


首先讲讲饿汉式,因为这个最简单:

package cn.handcool.singlton;
public class SingleTon {
private static SingleTon instance = new SingleTon();
private SingleTon(){}
public static SingleTon getInstance(){

return instance;
}
}

懒汉式:


package cn.handcool.singlton;
public class SingleTon {
private static SingleTon instance ;
private SingleTon(){}
public static SingleTon getInstance(){
if(null==instance){
instance = new SingleTon();
}
return instance;
}
}

在java中有延迟加载的概念,懒汉式就是使用了延迟加载的思想

通俗点说,就是一开始不要加载资源或者手机,一直等到马上就要使用这个资源或者数据了,才去加载
所以也称lazy load,这个在实际开发中是一种很常见的思想,尽可能的节约资源


而懒汉式的延迟加载提现在那呢?

if(null==instance){
instance = new SingleTon();
}

上面的代码是只有去调用getInstance()方法才去创建SingleTon对象实例,如果不调用就不会创建对象,相对饿汉式就是一种延迟加载了,因为它不像饿汉式那样,一上来就创建对象缓存在内存中


懒汉式提现了java中的缓存思想,这在java中也有很多,比如android中加载图片,就是使用了缓存的思想,可以缓存在内存,或者sd卡以及其他存储空间,


缓存的思想:

简单的来讲,就是某些资源或者数据被频繁的使用,就不必每次访问网络去获取这些数据,第一效率会低,第二:节省了用户的流量,那我们就可以把这些数据缓存到内存中,每次操作到内存中 去找,内存中没有,就走网络去获取,第一次从网络获取后要设置到内存中,方便下次使用,缓存是一种典型的空间换时间的方案.


在java中一般使用集合来实现缓存,先看简单的例子:

JavaCache.java

<span style="font-size:24px;">/**
 * Java中缓存的基本实现示例
 */
public class JavaCache {

	public static Map<String,Object> map = new HashMap<String,Object>();
	/**
	 * 从缓存中获取值
	 * @param key 设置时候的key值
	 * @return key对应的Value值
	 */
	public static Object getValue(String key){
		//先从缓存里面取值
		Object obj = map.get(key);
		//判断缓存里面是否有值
		if(obj == null){
			//如果没有,那么就去获取相应的数据,比如读取数据库或者文件
			//这里只是演示,所以直接写个假的值
			obj = key+",value";
			//把获取的值设置回到缓存里面
			map.put(key, obj);
		}
		//如果有值了,就直接返回使用
		return obj;
	}
}</span>
<span style="font-size:24px;">Client.java</span>
<span style="font-size:24px;">public class Client {
  public static void main(String[] args) {
   JavaCache.map.put("a", "a");
   JavaCache.map.put("b", "b");
   JavaCache.map.put("c", "c");
   JavaCache.map.put("d", "d");
   
   System.out.println(JavaCache.getValue("a"));
   System.out.println(JavaCache.getValue("b"));
   System.out.println(JavaCache.getValue("e"));
  }
}</span>
<span style="font-size:24px;">运行结果:</span>
<span style="font-size:24px;">a
b
e,value</span>
<span style="font-size:24px;">在map集合并没有存放key为"e"对应的值</span>
<span style="font-size:24px;">代码如下:</span>
<span style="font-size:24px;"></span>

<span style="font-size:24px;"></span>
<span style="font-size:24px;">if(obj == null){
			System.out.println("zou");
			//如果没有,那么就去获取相应的数据,比如读取数据库或者文件
			//这里只是演示,所以直接写个假的值
			obj = key+",value";
			//把获取的值设置回到缓存里面
			map.put(key, obj);
		}</span>

然后保存大哦哦map集合中,下次获取key为"e"的值就在map集合中了,


缓存的基本实现:

1:定义一个存放缓存数据的容器

2:先从缓存里面取值

3:判断缓存里面是否有值

4:如果有值了,就直接使用这个值

5:如果没有,那么就去获取相应的数据,或者是创建相应的对象

6:把获取的值设置回到缓存里面


现在利用缓存模拟单例的实现

Singleton.java

<span style="font-size:24px;">/**
 * 使用缓存来模拟实现单例
 */
public class Singleton {
	/**
	 * 定义一个缺省的key值,用来标识在缓存中的存放
	 */
	private final static String DEFAULT_KEY = "One";
	/**
	 * 缓存实例的容器
	 */
	private static Map<String,Singleton> map = new HashMap<String,Singleton>();
	/**
	 * 私有化构造方法
	 */
	private Singleton(){
		//
	}
	public static Singleton getInstance(){
		//先从缓存中获取
		Singleton instance = (Singleton)map.get(DEFAULT_KEY);
		//如果没有,就新建一个,然后设置回缓存中
		if(instance==null){
			instance = new Singleton();
			map.put(DEFAULT_KEY, instance);
		}
		//如果有就直接使用
		return instance;
	}
	
}</span>

上面就是使用缓存来实现单例模式的,也能确保类的实例仅有一个

现在从安全问题考虑单例的二种现实方式

1:饿汉式 这种是没有线程安全问题的,因为在getInstance()方法中直接返回了SingleTon对象,线程安全产生的条件是多个线程执行了同一段代码,但是饿汉式只有一行代码,所以不存在线程安全问题

2:懒汉式会出现安全问题,那我们分析分析怎么会出现安全问题

主要代码如下:

<span style="font-size:24px;">public static SingleTon getInstance(){
		if(null==instance){
			instance = new SingleTon();
		}
		return instance;
	}</span>
<span style="font-size:24px;">
</span>

比如说有A,B线程同时调用getInstance()方法

A线程执行到了 new SingleTon();但是并没有赋值给instance,这个时候刚好有个B线程进来,因为new的对象没有赋值给instance变量,所以instance还是为null,因此B线程还是会走if语句,那么这个时候A和B都是new SingleTon();对象并赋值给instance,那就是创建了2个对象了,和单例模式的定义并不符合,这就是懒汉式导致的线程安全问题,既然出现了问题,那么我们就想办法解决,

在线程中解决安全问题通常有2种方法

1:使用同步代码块

2:使用同步方法


如果使用同步方法解决安全问题,这很简单直接在getInstance()方法中添加synchronized关键字就可以,代码如下:

<span style="font-size:24px;">public synchronized static SingleTon getInstance(){
		if(null==instance){
			instance = new SingleTon();
		}
		return instance;
	}</span>
<span style="font-size:24px;">通过加一个关键字synchronized就轻而易举的解决了线程安全问题,问题解决了,那有没有比这更高效的方法呢?答案是有</span>
<span style="font-size:24px;">双重检查加锁判断比使用同步方法更高效</span>
<span style="font-size:24px;">所谓双重检查加锁机制,指的是:并不是每次进入getInstance()方法都需要同步,而是先不同步,进入方法后,先检查实例对象是否存在,如果不存在才进入下面的同步代码块,这是第一次检查,进入同步代码块后,再次检查实例对象是否存在,如果不存在,就在同步的情况下创建实例对象,这是第二次检查,这样一来,就只要同步一次了,从而减少了多次在同步情况下进行判断浪费时间</span>
<span style="font-size:24px;">
</span>
<span style="font-size:24px;">这就是双重检查加锁比同步方法效率更高德原因</span>
<span style="font-size:24px;">
</span>
<span style="font-size:24px;">代码如下:</span>
<pre class="java" name="code"><span style="font-size:24px;">public static  Singleton getInstance(){
		//先检查实例是否存在,如果不存在才进入下面的同步块
		if(instance == null){
			//同步块,线程安全的创建实例
			synchronized(Singleton.class){
				//再次检查实例是否存在,如果不存在才真的创建实例
				if(instance == null){
					instance = new Singleton();
				}
			}
		}
		return instance;
	}</span>
<span style="font-size:24px;">总结:使用双重检查加锁只需要同步一次创建了对象后,其他时候就不需要同步了,直接返回创建的对象实例即可!</span>

 














  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值