java - 设计模式 单例模式

菜鸟,如有不对的,请指教


单例模式是什么

单例模式是,一个类要保证在整个应用程序的生命周期中,在任何时刻,该类都只有一个实例对象(引用地址指向同一地方)


实现思路(要求)

1、外部不能构造该对象(就是外部不能new 对象;通过把无参参构造方法私有化实现)

2、类本身自己拥有构造方法,并且该构造方法必须自己创建自己的唯一实例

3、构造方法public修饰,允许整个系统获取类实例


为什么要用单例模式

1、控制实例产生的数量,达到节约资源的目的

2、数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信

(比如说我们在系统启动时,需要加载一些配置和属性,这些配置和属性是一定存在了,又是公共的,同时需要在整个生命周期中都存在,所以只需要一份就行,这个时候如果我在每次需要的时候就去new一个,再给他分配值,显然是浪费内存并且再赋值没什么意义,所以这个时候最好的方法是我们用单例模式去维持一份且仅这一份拷贝)

(在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象等等的常被设计成单例)

3、控制资源的使用,通过线程同步来控制资源的并发访问

单例模式最核心的一点是体现了面向对象封装特性中的“单一职责”和“对象自治”原则


单例模式的主要几种写法

1、懒汉式单例

线程不安全:普通懒汉式

线程安全:方法锁、双重检验锁、静态内部类

2、饿汉式单例

线程安全:静态对象

3、登记式单例


懒汉式和饿汉式的区别

1、加载

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

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


2、线程安全

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

2.2、懒汉式本身是非线程安全的,为了实现线程安全需要通过锁和静态内部类来实现(参考下面懒汉式的1.2,1.3,1.4实现方式


3、资源加载和性能

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

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

3.2.1、懒汉式1.2的实现方式

通过方法锁保证了线程安全,但每次调用都会加锁。而实际情况只需要第一次加锁,其它情况下都不用再加锁。及其影响性能

3.2.2、懒汉式1.3的实现方式

通过两次null判断,优化了1.2实现方式,只在第一次加锁,其它情况不再加锁,不会多余的损耗性能能

3.2.3、懒汉式1.4的实现方式

利用ClassLoader对static的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗



1、懒汉式


1.1、懒汉式 - 线程不安全

/**
 * 懒汉式 - 线程不安全
 * 
 * 这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题 - 线程不安全
 * 
 * 当有多个线程并行调用 getInstance()的时候,就会创建多个实例
 * 
 * 所以我们需要加锁,保证线程安全 - Singleton2
 * 
 * @author ershuai
 * @date 2017年11月17日 上午9:47:52
 */
public class Singleton1 {

	private static Singleton1 instance;
	
	// 外界不能造对象 把无参构造方法私有
	private Singleton1 (){}

	// 通过公共的方式对外提供 通过public修饰
	public static Singleton1 getInstance() {
		if (instance == null) {
			// 类本身要造一个 调用构造方法即可
			instance = new Singleton1();
		}
		return instance;
	}
}

测试

/**
 * 
 * 在多线程测试下,每个线程中,对象的的内存地址是不一样的
 * 
 * @author ershuai
 * @date 2017年11月17日 上午9:47:52
 */
public class Singleton1_Test {
	
	public static void main(String[] args) {
		Thread thread1 = new Thread(new TestSingleton1());
		thread1.start();
		Thread thread2 = new Thread(new TestSingleton1());
		thread2.start();
	}
}

class TestSingleton1 implements Runnable {
	@Override
	public void run() {
		Singleton1 singleton1 = Singleton1.getInstance();
		System.out.println(singleton1);
	}
}


1.2、懒汉式 - 锁 - 线程安全

/**
 * 懒汉式 - 线程安全
 * 
 * 虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。
 * 
 * 因为是方法锁,所以每一次调用getInstance()都会加锁。而我们只希望第一次创建实例的时候加锁保证线程安全,其它时候读取实例,不再加锁
 * 
 * 所以我们需要实例在为null的时候,加锁实例化对象。不为null的时候可以直接获取 - Singleton3
 * 
 * @author ershuai
 * @date 2017年11月17日 上午9:55:46
 */
public class Singleton2 {

	// 用static申明,类在加载时就初始化
	private static Singleton2 instance;
	
	// 外界不能造对象 把无参构造方法私有
	private Singleton2() {}

	public static synchronized Singleton2 getInstance() {
		if (instance == null) {
			instance = new Singleton2();
		}
		return instance;
	}
}


测试

/**
 * 
 * 在多线程测试下,每个线程中,对象的的内存地址是一样的
 * 
 * @author ershuai
 * @date 2017年11月17日 上午9:55:46
 */
public class Singleton2_Test {
	
	public static void main(String[] args) {
		Thread thread1 = new Thread(new TestSingleton2());
		thread1.start();
		Thread thread2 = new Thread(new TestSingleton2());
		thread2.start();
	}
}

class TestSingleton2 implements Runnable {
	@Override
	public void run() {
		Singleton2 singleton2 = Singleton2.getInstance();
		System.out.println(singleton2);
	}
}


1.3、懒汉式 - 双重检验锁 - 线程安全

/**
 * 懒汉式 - 双重检验锁 - 线程安全
 * 
 * 双重检查,這种方式就很好了,在保证了线程安全的情况下,还高效
 * 
 * 判断为null,加锁进行初始化。当一个线程拿到锁,进行初始化的时候,需要再次判断是否为null,不为null进行初始化
 * 
 * 为什么要在锁里面再次判断是否为null呢?	
 * 如果A、B两个线程同时进来,在代码1处,A线程获取到了锁,进入代码块进行了实例化,然后释放锁,
 * B拿到锁对象,进入代码块,其实这时候对象已实例化了,就需要再次判断,避免再次实例化对象
 * 
 * 为什么要用volatile申明
 * 用volatile申明,是为了保证顺序一致性(编译器不进行优化)
 * 
 * @author ershuai
 * @date 2017年11月17日 上午10:02:07
 */
public class Singleton3 {

	// 声明成 volatile 保证顺序一致性(编译器不进行优化)
	private volatile static Singleton3 instance;
	
	// 外界不能造对象 把无参构造方法私有
	private Singleton3() {}

	public static Singleton3 getInstance() {
		if (instance == null) {
			synchronized (Singleton3.class) {		// 1
				if (instance == null) {				// 2
					instance = new Singleton3();	// 3
				}
			}
		}
		return instance;
	}
}

测试
/**
 * 
 * 在多线程测试下,每个线程中,对象的的内存地址是一样的
 * 
 * @author ershuai
 * @date 2017年11月17日 上午9:55:46
 */
public class Singleton3_Test {
	
	public static void main(String[] args) {
		Thread thread1 = new Thread(new TestSingleton3());
		thread1.start();
		Thread thread2 = new Thread(new TestSingleton3());
		thread2.start();
	}
}

class TestSingleton3 implements Runnable {
	@Override
	public void run() {
		Singleton3 singleton3 = Singleton3.getInstance();
		System.out.println(singleton3);
	}
}


1.4、懒汉式 - 静态内部类 - 线程安全

/**
 * 懒汉式 - 静态内部类 - 线程安全
 * 
 * 这种写法使用JVM本身机制保证了线程安全问题
 * 由于 SingletonHolder是私有的,除了 getInstance()之外没有办法访问它,因此它是懒汉式的;
 * 同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK版本。
 * 
 * @author ershuai
 * @date 2017年11月17日 上午10:37:19
 */
public class Singleton4 {

	private static class SingletonHolder {
		private static final Singleton4 INSTANCE = new Singleton4();
	}
	
	// 外界不能造对象 把无参构造方法私有
	private Singleton4() {}

	public static final Singleton4 getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

测试

/**
 * 
 * 在多线程测试下,每个线程中,对象的的内存地址都是一样的
 * 
 * @author ershuai
 * @date 2017年11月17日 上午9:55:46
 */
public class Singleton4_Test {
	
	public static void main(String[] args) {
		Thread thread1 = new Thread(new TestSingleton4());
		thread1.start();
		Thread thread2 = new Thread(new TestSingleton4());
		thread2.start();
	}
}

class TestSingleton4 implements Runnable {
	@Override
	public void run() {
		Singleton4 singleton4 = Singleton4.getInstance();
		System.out.println(singleton4);
	}
}


2、饿汉式

/**
 * 饿汉式 - 静态对象 - 线程安全
 * 
 * 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的
 * 
 * @author ershuai
 * @date 2017年11月17日 上午10:40:27
 */
public class Singleton1 {

	private static final Singleton1 singleton = new Singleton1();

	private Singleton1() {}

	// 共有方法
	public static Singleton1 getInstance() {
		return singleton;
	}
}

测试

/**
 * 
 * 在多线程测试下,每个线程中,对象的的内存地址都是一样的
 * 
 * @author ershuai
 * @date 2017年11月17日 上午9:47:52
 */
public class Singleton1_Test {
	
	public static void main(String[] args) {
		Thread thread1 = new Thread(new TestSingleton1());
		thread1.start();
		Thread thread2 = new Thread(new TestSingleton1());
		thread2.start();
	}
}

class TestSingleton1 implements Runnable {
	@Override
	public void run() {
		Singleton1 singleton1 = Singleton1.getInstance();
		System.out.println(singleton1);
	}
}


3、登记式

/**
 * 登记式 - 线程安全
 * 
 * @author ershuai
 * @date 2017年11月17日 上午11:15:23
 */
public class Singleton1 {

	private static Map<String, Singleton1> map = new HashMap<String, Singleton1>();

	static {
		Singleton1 single = new Singleton1();
		map.put(single.getClass().getName(), single);
	}

	// 保护的默认构造子(对子类来说,不能用private修饰)
	protected Singleton1(){}

	// 开放一个公有方法,判断是否已经存在实例,有返回,没有新建一个在返回
	public static Singleton1 getInstance(String name) {
		if (name == null) {
			name = Singleton1.class.getName();
			System.out.println("name == null" + "--->name=" + name);
		}
		if (map.get(name) == null) {
			try {
				map.put(name, (Singleton1) Class.forName(name).newInstance());
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
		return map.get(name);
	}
    
	public Map<String, Singleton1> getMap() {
		return map;
	}
}

/**
 * 
 * @author ershuai
 * @date 2017年11月17日 上午11:15:23
 */
public class Singleton1_1 extends Singleton1 {
	
	public static Singleton1_1 getInstance() {
		return (Singleton1_1) Singleton1_1.getInstance(Singleton1_1.class.getName());
	}
	
	public String say() {
		return "---->我是 Singleton1 的第一个子类 Singleton1_1";
	}
}

/**
 * 
 * @author ershuai
 * @date 2017年11月17日 上午11:15:23
 */
public class Singleton1_2 extends Singleton1 {
	
	public static Singleton1_2 getInstance() {
		return (Singleton1_2) Singleton1_2.getInstance(Singleton1_2.class.getName());
	}
	
	public String say() {
		return "---->我是 Singleton1 的第二个子类 Singleton1_2";
	}
}

测试




参考:
http://blog.youkuaiyun.com/jason0539/article/details/23297037/
http://blog.youkuaiyun.com/ljhljh8888/article/details/8017701/




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值