浅析Java中的23种设计模式之----单例模式

单例模式作为23种设计模式之一,有着它特定的需求场景,比如一些内部数据结构不需要发生变化的实例(一些工具类)。

单例模式的核心就是只允许有一个该类的静态实例,并且这个静态实例必须由这个类自己对外提供,也就是说只能由这个类自己实例化自己。由于只允许这个类实例化自己,就意味着我们在其他类里不能使用new关键字为这个类实例化,所以这个类的构造函数应该用private修饰。

单例模式分为三种模式,分别为饿汉式,懒汉式,静态内部类。

1.饿汉式

饿汉式单例中,类在一进入内存就完成了对自身静态实例的初始化,永久驻存在内存中。所以即使有多个线程在同一时刻调用该类的获取实例方法,也不会存在该对象再次被初始化的情况。饿汉式是线程安全的。

饿汉实例

package com.huang.Instance;

//饿汉模式
public class HungryInstance {
	
	private HungryInstance(){};
	
	private static HungryInstance hungryInstance = new HungryInstance();
	
	public static HungryInstance getInstance(){
		return hungryInstance;
	}

}

获取该实例只需要通过HungryInstance.getInstance()方法即可获得该类的唯一静态实例。

2.懒汉式

在懒汉式单例中,类不会在被加载进内存的时候直接将自身实例初始化,而是在对外提供的获取实例的方法内进行初始化。具体实现如下

package com.huang.Instance;

//懒汉模式
public class LazyInstance {

	private LazyInstance() {
	};

	private static LazyInstance lazyInstance = null;

	public static LazyInstance getInstance() {
		if (lazyInstance == null) {
			lazyInstance = new LazyInstance();
		}
		return lazyInstance;
	}

}

看上去没什么问题,相较于饿汉模式也只是实例化时间不同而已,的确,在单线程的情况下,饿汉式和懒汉式没有区别,但是在多线程下懒汉式会存在线程安全问题,通过getInstance方法提供的实例将不再是唯一实例,违背了单例模式的设计原则。下面是一个多线程测试类:

package com.huang.test;

import com.huang.Instance.LazyInstance;

public class Test {

	public static void main(String[] args) {

        //线程1
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println(LazyInstance.getInstance());
			}
		}).start();

        //线程2
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println(LazyInstance.getInstance());
			}
		}).start();

        //线程3
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println(LazyInstance.getInstance());
			}
		}).start();
	}
}

该测试类的main方法中有三个线程,分别会打印出它们所获取到的实例在堆(heap)中的内存地址,让我们来看一下结果:

很明显内存地址发生了变化,这是因为线程的执行速率非常快,快过了实例化的速率。比如线程1进入getInstance的if条件语句,条件成立开始实例化,此时线程2也进入if条件语句,由于线程1还未完成实例化,所以创建的对象依旧为null,所以线程2的if条件也成立,也会进行实例化,在getInstance方法中加入线程睡眠会将问题放大的更清晰。

package com.huang.Instance;

//懒汉模式
public class LazyInstance {

	private LazyInstance() {
	};

	private static LazyInstance lazyInstance = null;

	public static LazyInstance getInstance() {
		if (lazyInstance == null) {
try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			lazyInstance = new LazyInstance();
		}
		return lazyInstance;
	}

}

输入的结果:

这个时候我们就需要通过线程锁+双重判断的方式来确保所提供的实例是全局唯一的,具体方式如下:

package com.huang.Instance;

//懒汉模式
public class LazyInstance {

	private LazyInstance() {
	};

	private static LazyInstance lazyInstance = null;

	public static LazyInstance getInstance() {
		if (lazyInstance == null) {
			//确保锁的对象应该是类对象,而不是实例,同一时刻只能有一个线程访问这个类。
			synchronized (LazyInstance.class) {
				if (lazyInstance == null) {
					lazyInstance = new LazyInstance();
				}
			}
		}
		return lazyInstance;
	}

}

现在就不会出现实例多次被初始化的情况了。

懒汉式存在线程安全问题,即在多线程的情况下可能会出现实例多次被创建的情况,因此不推荐使用懒汉式。如果使用了懒汉式,需要加同步锁来解决线程安全问题。

3.静态内部类

静态内部类也是线程安全的,具体实现如下:

package com.huang.Instance;

//静态内部类实现
public class InnerClassInstance {
	
	private InnerClassInstance() {
	};

	
	private static class InnerClass{
		private static InnerClassInstance innerClassInstance = new InnerClassInstance();
	}
	
	public InnerClassInstance getInstance(){
		return InnerClass.innerClassInstance;
	}

}

在getInstance方法中会先将静态内部类InnerClass加载入内存,在加载的同时完成实例化。

单例模式可以减少代码中new关键字的出现,减少程序对内存的占用。但是对于扩展显得非常的困难,对于一些需要增加属性或方法的类,只能通过修改类的代码来实现。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值