单例模式

1. 什么是单例模式

确保一个类只有一个实例,并且提供一个全局访问点。

2. 为什么使用单例模式?

有一些对象其实我们只需要一个。比如线程池、缓存、还有操作系统中的任务管理器等。
以任务管理器为例,若是能打开过个任务管理器,每个任务管理器显示的内容一致,会造成资源的浪费;若多个任务管理器显示的内容不一致,这与实际不符,也会造成用户的困惑。
所以单例模式就是为了解决这些问题。

3. 单例模式的实现?

  1. 饱汉式
package com.fine.singleton;
//饱汉式
public class Singleton1 {
	private static Singleton1 instance = new Singleton1();
	private Singleton1() {}
	public static Singleton1 getInstance() {
		return instance;
	}	
}

单例模式首先为了让外界无法创建自己的对象,先将自己的构造器初始化。在类加载时就自己创建了一个对象,外界可以通过一个get方法来获取这个对象。

这个模式的优点是线程安全的,在加载类的过程中就产生了这个对象,无论多少个线程来获取对象,获取的都是这个创建好的对象。
这种模式有一些缺点,如果这个对象在创建过程中需要耗费大量的资源,但是后续过程中又一直没有用到这个对象,那么会造成资源浪费,可以使用饿汉式来解决这个问题

  1. 饿汉式
package com.fine.singleton;
//饿汉式  
public class Singleton2 {
	private static Singleton2 instance;
	private Singleton2() {}
	public static Singleton2 getInstance() {
		if (instance == null) {
			instance = new Singleton2();
		}
		return instance;
	}	
}

饿汉式在加载类的时候并不创建对象,而是在需要的时候才进行创建。
优点是避免了对不需要的对象在加载类时创建造成的资源浪费。
缺点是县城不安全的,如果有连个线程同时进入到 if语句中,则会造成实例的重复创建。为了解决这个问题可以做以下改进。

  1. 线程安全的饿汉式
package com.fine.singleton;
//饿汉式  线程安全
public class Singleton2 {
	private static Singleton2 instance;
	private Singleton2() {}
	public synchronized static Singleton2 getInstance() {
		if (instance == null) {
			instance = new Singleton2();
		}
		return instance;
	}	
}

在getInstance中加入了synchronized 可以保证只有一个线程可以进入该方法,所以是线程安全的。
缺点是在第一次调用getInstance方法时就已经创建了对象,在此之后就不需要进行同步了,可以进行直接返回,但是在这个类中以后还会频繁的调用这个同步方法,会造成效率的降低,为了解决这个问题,可以采用双重检查加锁。

  1. 双重检查加锁
package com.fine.singleton;
//饿汉式  
public class Singleton4 {
	private static volatile Singleton4 instance;
	private Singleton4() {}
	public static Singleton4 getInstance() {
		if (instance == null) {
			synchronized(Singleton4.class) {
				if (instance == null) {
					instance = new Singleton4();
				}
			}	
		}
		return instance;
	}	
}

采用双重加锁,只有当前实例还未创建才会进入同步块,如果此时instance依然是null,才会创建对象。第二次判断是为了保证线程安全,如果两个线程同时进入到第一个判断里面,在其中一个线程创建好对象后会释放锁,另一个线程获得锁,如果此时没有判断,那么这个线程会继续创建对象,第二次判断可以避免这种情况的发生。

  1. 枚举式
package com.fine.singleton;

public enum Singleton5 {
	INSTANCE;
    public Singleton5 getInstance(){
        return INSTANCE;
    }
}
/*编译后
 * public final class  Singleton5 extends Enum< Singleton5 > {
        public static final  Singleton5  SINGLETON5;
        public static  EnumSingleton[] values();
        public static  EnumSingleton valueOf(String s);
        static {};
}
 * 
 * */

为什么使用枚举类来实现单例模式呢?
1: 以上的类如果可以序列化,任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。在对这个对象进行序列化和反序列化,会生成一个和原来不同的对象;
2: 以上的类都可以通过反射来获取私有的构造器来创建新对象。

反射是通过获取构造器,调用Constructor类的newInstance方法创建对象,在这个方法中,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。保证了前后的一致性。

4. 单例模式优缺点

  • 优点

    (1)由于单例模式在内存中只有一个实例,减少了内存开支。
    (2)由于单例模式只生成一个实例,当一个对象的产生需要比较多的资源时,比如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
    (3)单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
    (4)单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。

  • 缺点
    (1)由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    (2)单例类的职责过重,在一定程度上违背了“单一职责原则”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值