设计模式学习笔记03 - Java版之单例模式

1. 单例模式介绍

单例模式就是采用一定的方法, 保证整个软件系统中, 对某个类只能存在一个对象实例, 并且该类只存在一个取得其对象实例的方法

2. 单例模式的八种方式

  1. 饿汉式 - 静态常量
  2. 饿汉式 - 静态代码块
  3. 懒汉式 - 线程不安全
  4. 懒汉式 - 线程安全, 同步方法
  5. 懒汉式 - 线程安全, 同步代码块
  6. 双重检查
  7. 静态内部类
  8. 枚举

2.1 饿汉式 - 静态常量

  1. 构造器私有化 - 防止new对象
  2. 类的内部创建对象
  3. 向外暴露一个公共方法 - getInstance()
package com.liyang;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2);

	}

}

// 饿汉 - 静态实例
class Singleton {
	// 1. 私有构造器
	private Singleton() {
		
	}
	// 2. 创建对象
	private final static Singleton instance = new Singleton();
	// 3. 外部获取对象的方法
	public static Singleton getInstance() {
		return instance;
	}
	 
}

优点: 写法简单, 在类装载的时候就完成了实例化, 避免了线程同步问题
缺点: 在类装载完成了实例化没有达到懒加载的效果, 如果始终未用到这个实例会造成内存浪费

2.2 饿汉式 - 静态代码块

package com.liyang;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2);

	}

}

// 饿汉 - 静态代码块
class Singleton {
	// 1. 私有构造器
	private Singleton() {
		
	}
	// 2. 创建对象
	private final static Singleton instance;
	static {
		instance = new Singleton();
	}
	// 3. 外部获取对象的方法
	public static Singleton getInstance() {
		return instance;
	}
	 
}

和上面的饿汉式 - 静态常量基本没什么区别, 只不过是将类的实例化放在了静态代码块中, 优缺点一样.

2.3 懒汉式 - 线程不安全

package com.liyang;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2);

	}

}

// 懒汉 - 线程不安全
class Singleton {
	// 1. 私有构造器
	private Singleton() {
		
	}
	// 2. 创建对象
	private static Singleton instance;
	// 3. 当使用该方法时, 才会创建Instance
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
	 
}

优点: 懒加载
缺点: 单线程下使用, 实际开发中不要用这种方式

2.4 懒汉式 - 线程安全

package com.liyang;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2);

	}

}

// 懒汉 - 线程安全
class Singleton {
	// 1. 私有构造器
	private Singleton() {
		
	}
	// 2. 创建对象
	private static Singleton instance;
	// 3. 当使用该方法时, 才会创建Instance 加入同步代码保证线程安全
	public static synchronized Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
	 
}

解决了线程安全问题, 但是效率太低. 多线程环境只有一个在执行getInstance方法, 其他的线程在等待. 实际开发中不推荐使用

2.5 懒汉式 - 同步代码块

package com.liyang;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2);

	}

}

// 懒汉 - 同步代码块
class Singleton {
	// 1. 私有构造器
	private Singleton() {
		
	}
	// 2. 创建对象
	private static Singleton instance;
	// 3. 当使用该方法时, 才会创建Instance
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized(Singleton.class) {
				instance = new Singleton();
			}
		}
		return instance;
	}
	 
}

上面的这种写法实际上是有问题的, 当多个线程同时进入了getInstance的方法, 堵在了同步代码块前面, 一个线程创建了instance, 第二个线程等第一个执行完又创建了instance, 这就不是单例模式了

2.6 双重检查

package com.liyang;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2);

	}

}

// 双重检查
class Singleton {
	// 1. 私有构造器
	private Singleton() {
		
	}
	// 2. 创建对象 
	// volatile保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
	private static volatile Singleton instance;
	// 3. 当使用该方法时, 才会创建Instance
	public static Singleton getInstance() {
		if (instance == null) {
			synchronized(Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
	 
}

双重检查是在多线程开发中经常使用到的场景, 进行两次检查就能保证线程安全了
线程安全 延迟加载 效率高 实际开发推荐使用

2.7 静态内部类

package com.liyang;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		Singleton instance2 = Singleton.getInstance();
		System.out.println(instance == instance2);

	}

}

// 静态内部类
class Singleton {
	// 1. 私有构造器
	private Singleton() {
		
	}
	// 2. 创建静态内部类
	private static class SingletonInstance {
		private final static Singleton instance = new Singleton();
	}
	// 3. 当使用该方法时, 才会创建Instance
	public static Singleton getInstance() {
		return SingletonInstance.instance;
	}
	 
}
  1. 当外部类在加载的时候, 内部类不会被加载, 只有在被调用的时候内部类才会被加载, 也就是说懒加载没有问题
  2. JVM在进行类装载的时候是线程安全的, 也就是说静态内部类不会有多线程问题

2.8 枚举

package com.liyang;

public class SingletonTest {

	public static void main(String[] args) {
		Singleton instance = Singleton.INSTANCE;
		Singleton instance2 = Singleton.INSTANCE;
		System.out.println(instance == instance2);

	}

}

// 枚举
enum Singleton {
	 INSTANCE;
	public void syaOK() {
		System.out.println("OK...");
	}
}

不仅能避免多线程同步问题, 而且能防止反序列化重新创建对象, 这种方式是Java的作者推荐的方式

3. 单例模式注意事项

  1. 单例模式保证了系统内存中该类只存在一个对象, 节省了系统资源. 对于需要频繁创建销毁的对象, 使用单例模式能提高系统性能
  2. 使用单例类不能new, 要通过相应地获取对象的方法获取对象
  3. 场景: 频繁进行创建和销毁的对象 创建对象耗时过多或者耗费资源过多 工具类 频繁访问数据库对象等
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值