java单例设计
什么是单例?
单例模式是一种创建型设计模式,它保证一个类在整个系统中只有一个实例,并提供一个全局访问点来访问该实例,当需要共享某个资源时,使用单例模式可以确保系统中只有一个实例在工作。
从实现角度讲,在单例模式中,为了防止外部代码随意创建该类的多个实例,类的构造函数通常是私有的,通过一个静态方法来获取这个唯一的实例。例如:
public class sinMain {
private static sinMain instance;
private sinMain() {
}
public static sinMain getInstance() {
if (instance == null) {
instance = new sinMain();
}
return instance;
}
}
在这个例子里, getInstance 方法用于获取单例对象,第一次调用时,如果实例还不存在,就创建一个,后续调用则直接返回已经存在的实例。
懒汉式
以上就是线程不安全懒汉式的演示,在单例模式的语境下,“懒汉”的意思是实例在需要的时候才会被创建,就像一个“懒人”,不到必要时刻不会行动。
懒汉式的优点是可以节省系统资源,因为只有在真正需要使用这个单例对象时才会创建它,一个应用程序中如果有一个复杂的配置读取类,这个类的实例化过程很消耗资源,并且可能在程序运行过程中不一定马上就会用到,使用懒汉式就很合适。
不过,懒汉式单例也有缺点。在多线程环境下,如果没有合适的同步机制,可能会导致创建多个实例,违背单例的原则。例如,多个线程同时访问获取实例的方法,可能会同时判断实例不存在,然后都去创建实例,所以在多线程环境下使用懒汉式,需要考虑线程安全问题,通常会使用锁等机制来确保只有一个实例被创建,就像这样:
public class sinMain {
private static sinMain instance;
private sinMain() {
}
public synchronized static sinMain getInstance() {
if (instance == null) {
instance = new sinMain();
}
return instance;
}
}
getInstance
方法是返回唯一实例的地方,因此它必须保证在多线程环境下,只有一个线程能够创建并返回单一的实例,使用 synchronized
就是为了保证在某一时刻,只有一个线程能够执行 getInstance
方法,从而避免并发时可能产生多个实例的情况。
虽然 synchronized
可以保证线程安全,但它也会带来性能开销,因为每次进入 getInstance
方法时,线程需要获取锁,其他线程必须等待锁释放,这意味着,如果有多个线程频繁调用 getInstance
,就可能会导致性能瓶颈,影响程序的效率,为了避免这种性能问题,通常可以采用 双重检查锁 的方式来优化,避免每次调用都进入同步块,提升性能。
双重检查锁
我们可以通过引入双重检查锁来减少同步的开销,在第一次检查 instance == null
时不加锁,只在确认 instance
为空时,再进入同步块,这样,如果 instance
已经被初始化,后续的调用将不会进入同步块,从而提高性能。
优化后的代码如下:
public class sinMain {
private static volatile sinMain instance;
private sinMain() {
}
public synchronized static sinMain getInstance() {
if (instance == null) {
synchronized (sinMain.class) {
if (instance == null) {
instance = new sinMain();
}
}
}
return instance;
}
}
*volatile
关键字:instance
变量使用volatile
关键字,以确保多个线程能够正确地读取和写入该变量,避免由于 JVM 的指令重排序或缓存导致的问题。
在不加锁的情况下,先判断 instance == null
,如果已经有实例存在,直接返回,这样避免了不必要的同步开销,此次检查为 null
时,才进入 synchronized
区域进行加锁,加锁后,再次检查 instance == null
,如果仍然为 null
,则创建实例。
这样,只有在 instance
为 null
时才会进行加锁和实例化,而一旦 instance
已经被创建,后续的访问将直接返回已创建的实例,避免了不必要的同步。
静态内部类
静态内部类方法是一种较为优雅的实现方式,既可以懒加载,又能保证线程安全,此方式依赖于类加载机制来确保线程安全,因此避免了同步的开销。
以下是一个静态内部类单例实现:
public class sinMain {
private sinMain() {
}
public static class staticClass{
private static final sinMain main = new sinMain();
}
public static sinMain getInstance() {
return staticClass.main;
}
}
在以上示例中,sinMain类有一个私有的构造函数,防止外部直接创建实例。
staticclass这个静态内部类包含了一个static final类型的sinMain实例main,由于 staticclass静态内部类,只有在 getInstance 方法被调用时(即第一次访问), staticclass类才会被加载,此时main会被初始化,并且因为是 final 的,它只会被初始化一次,后续再调用 getInstance方法时,只会返回已经初始化好的main,从而保证了单例的特性。
优点:
种实现方式是线程安全的,因为类的静态初始化由Java虚拟机保证,在多线程环境下,JVM会保证staticclass类只被加载一次,也就保证了main只被初始化一次。
只有在第一次调用 getInstance方法时,单例实例才会被创建,这符合懒加载的原则,节省了资源,避免了在不需要使用单例对象时就提前创建的情况。
枚举单例
枚举类型本身就具有一些特性可以保证单例的实现。
以下是一个枚举单例的代码:
public enum SingletonEnum {
INSTANCE;
public void singletonMethod() {
System.out.println("枚举单例方法");
}
}
在这个枚举中, INSTANCE 就是单例对象。它是枚举类型SingletonEnum的唯一实例。
优点:
枚举单例和静态内部类单例一样,是天然线程安全的,除此之外,它还可以防止反射和序列化破坏,普通的单例模式可能会被反射机制通过调用私有构造函数来创建多个实例,而枚举单例则不会出现这种情况,因为Java不允许通过反射来创建枚举类型的实例。
当一个对象被序列化和反序列化时,普通单例可能会产生多个实例,但是对于枚举单例,Java在序列化和反序列化枚举时,会保证只有一个实例存在。
总结
单例设计适用于数据库连接池、配置管理器、日志记录器等系统中需要唯一实例的场景,能够有效控制资源分配和使用,在开发过程中合理使用单例,可以确保系统关键资源的一致性与高效利用。