单例模式的优点
- 单例模式的使用可以减少内存的开支、减少系统性能的消耗、避免资源的多重占用,例如一个写文件动作,由于只有一个实例存在于内存中,避免了对同一个资源文件同时写操作
单例模式的使用场景
- 要求生成唯一序列号的环境
- 在项目中需要一个共享访问点或共享数据,例如一个WEB页面的计数器,可以每次不用将刷新数据记录到数据库中,使用单例模式保持计数器的值,并且保证是线程安全的
- 创建一个对象需要消耗的资源过多
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)
单例模式的实现方式
饿汉式
/**
* @author cuckooYang
* @create 2020-08-03 14:36
* @description 饿汉式案例实现(线程安全)
**/
public class IdGenerator {
private Long id = new Random().nextLong();
private static IdGenerator instance = new IdGenerator();
private IdGenerator() {
}
public static IdGenerator getInstance(){
return instance;
}
public Long getId(){
return id;
}
}
public class IdGeneratorMain1 {
public static void main(String[] args) {
for(int i =0; i< 30; i++){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Long id = IdGenerator.getInstance().getId();
System.out.println(Thread.currentThread().getName()+" "+id+"");
}
});
thread.start();
}
}
}
双重检测
/**
* @author cuckooYang
* @create 2020-08-03 15:12
* @description 双重检测
**/
public class IdGenerator {
private Long id = new Random().nextLong();
private static IdGenerator instance;
private IdGenerator(){}
public static IdGenerator getInstance(){
if(instance == null){
synchronized (IdGenerator.class){
if(instance == null){
instance = new IdGenerator();
}
}
}
return instance;
}
public Long getId(){
return id;
}
}
/**
* @author cuckooYang
* @create 2020-08-03 15:24
* @description
**/
public class IdGeneratorMain {
public static void main(String[] args) {
for(int i=0; i<10; i++){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
IdGenerator idGenerator = IdGenerator.getInstance();
System.out.println(Thread.currentThread().getName()+" "+idGenerator.getId());
}
});
thread.start();
}
}
}
静态内部类
/**
* @author cuckooYang
* @create 2020-08-03 15:41
* @description 静态内部类
**/
public class IdGenerator {
private Long id = new Random().nextLong();
private IdGenerator(){}
private static class SingletonHolder{
private static final IdGenerator instance = new IdGenerator();
}
public static IdGenerator getInstance(){
return SingletonHolder.instance;
}
public Long getId(){
return id;
}
}
SingletonHolder是一个静态内部类,当外部类IdGenerator类被加载时,并不会创建SingletonHolder实例对象,只有当调用getInstance()方法时,SingletonHolder才会被加载,这个时候才会创建instance,instance的唯一性和创建线程的安全性都是由JVM来保证的,这种方法即实现了线程安全,又能做到延迟加载
枚举方式
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象,关于单例与序列化的问题可以查看这一篇文章《单例与序列化的那些事儿》)
/**
* 通过枚举方式
*/
public enum IdGenerator {
INSTANCE;
private Long id = new Random().nextLong();
public Long getId(){
return id;
}
}
/**
* @author cuckooYang
* @create 2020-08-03 14:49
* @description
**/
public class IdGeneratorMain1 {
public static void main(String[] args) {
for(int i =0; i< 30; i++){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
IdGenerator instance = IdGenerator.INSTANCE;
System.out.println(Thread.currentThread().getName()+" "+instance.getId());
}
});
thread.start();
}
}
}
单例存在的问题
- 单例对于OOP的特性支持不好
- IdGenerator的使用方式违背了基于接口而非实现的设计原则,也违背了广义上的理解OOP抽象特性。单例对于继承多态特性的支持不好。
- 单例会隐藏类之间的依赖关系
- 通过构造函数,参数传递等方式声明的类之间的依赖关系,我们可以通过函数的定义来查看,但是,单例类不需要创建,如果代码比较复杂,这种调用关系就会比较隐蔽
- 单例对于代码的扩展性不好
- 如果哪天我们需要创建多个实例,对代码的改动量就比较大,比如数据库连接池
- 单例对于代码的可测试性不友好
- 单例不支持有参数的构造参数
线程间创建唯一对象
/**
* @author cuckooYang
* @create 2020-08-04 14:21
* @description 线程间创建唯一对象
**/
public class IdGenerator {
private Long id = new Random().nextLong();
private static ThreadLocal<IdGenerator> threadLocal = new ThreadLocal<>();
private IdGenerator(){}
public static IdGenerator getInstance(){
if(threadLocal.get() == null){
threadLocal.set(new IdGenerator());
}
System.out.println(Thread.currentThread().getId()+" - "+threadLocal.get().hashCode());
return threadLocal.get();
}
public long getId(){
return id;
}
}
/**
* @author cuckooYang
* @create 2020-08-04 14:27
* @description
**/
public class ThreadMain {
public static void main(String[] args) {
for(int i=0; i<50; i++){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Long id1 = IdGenerator.getInstance().getId();
Long id2 = IdGenerator.getInstance().getId();
System.out.println(Thread.currentThread().getId()+" id1:"+id1+" id2:"+id2);
}
});
thread.start();
}
}
}
多例模式
/**
* @author cuckooYang
* @create 2020-08-04 14:56
* @description 多例模式
**/
public class Emperor {
private static int maxNumOfEmperror =2;
private static List<String> nameList = new ArrayList<>();
private static List<Emperor> emperors = new ArrayList<>();
private static int count = 0;
static{
for(int i=0;i< maxNumOfEmperror;i++){
emperors.add(new Emperor("皇帝"+i+"号"));
}
}
private Emperor(){}
private Emperor(String name){
nameList.add(name);
}
public static Emperor getInstance(){
Random random = new Random();
count = random.nextInt(maxNumOfEmperror);
System.out.println(emperors.get(count).hashCode());
return emperors.get(count);
}
public void say(){
System.out.println(nameList.get(count));
}
}
/**
* @author cuckooYang
* @create 2020-08-04 15:03
* @description
**/
public class ThreadMain {
public static void main(String[] args) {
for(int i=0; i<50; i++){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Emperor.getInstance().say();
}
});
thread.start();
}
}
}