单例模式是一种创建型模式。该模式中,构造方法是私有的,类内自己创建实例化对象,并返回给外部调用。通过加锁能够保证该类只有一个实例化对象,节省系统资源和开销。
意图:保证一个类仅有一个实例,提供给外部调用。
主要解决:避免类被频繁地创建与销毁,增加性能消耗;避免内存中保存多份实例,增加内存开销。
何时使用:需要控制实例数目,节省系统资源的场景。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数私有化,类内自己构造实例,
应用实例: 1、Windows的Task Manager(任务管理器) 2、多线程的线程池 3、网站的计数器
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
注意事项:getInstance() 方法需要使用synchronized关键字保证线程安全,避免多线程场景下实例化多个对象。
2、案例实现在具体实现上,单例模式存在几种不同的实现方式。
方式一:Lazy loading + 非线程安全
实例化的动作在getInstance()
函数中进行,属于Lazy loading。不使用synchronized
关键字,所以是非线程安全的。
/**
* Singleton.java
*/
public class Singleton
{
private static Singleton singleton;
public static Singleton getInstance()
{
if (singleton == null)
{
singleton = new Singleton();
}
return singleton;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
方式二:Lazy loading + 线程安全
实例化的动作在getInstance()
函数中进行,属于Lazy loading。使用synchronized
关键字,所以是线程安全的。
/**
* Singleton.java
*/
public class Singleton
{
private static Singleton singleton;
public static synchronized Singleton getInstance()
{
if (singleton == null)
{
singleton = new Singleton();
}
return singleton;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
方式三:非Lazy loading + 线程安全
实例化的动作在singleton
定义时进行,所以是在类加载时生成对象,直接占用内存空间。这种使用方式不需要加锁,所以执行效率比方式二要高。
/**
* Singleton.java
*/
public class Singleton
{
private static Singleton singleton = new Singleton();
public static synchronized Singleton getInstance()
{
return singleton;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
方式四:双重检查锁定
在getInstance()
方法中,判断当singleton
为空需要实例化的时候再进行加锁,减小了锁的粒度,相对于直接加锁性能上会更高。
new Singleton()
操作是非原子的。所以用 volatile
关键字修饰singleton
。volatile
关键字影响内存可见性,保证线程对被修饰变量的所有更改都直接映射到JMM的主内存中,并且保证操作的有序性。在多线程环境下,如果不加volatile
,那么指令的执行次序是存在乱序的。对于new Singleton()
操作,最终翻译成字节码,是包含多条指令的。如果线程A和线程B都访问到单例,线程A获取到锁,在进行单例的初始化。由于指令的乱序执行,有可能是先分配了空间(这时候instance
的值已经不是null
了),再执行其他指令进行初始化。如果在分配完空间之后,其他指定执行之前,线程A被踢出了CPU,这时候线程B执行,检查内存中instance
已经不是null
,则获取并使用。但是此时instance
并没有初始化完成,导致程序错误。
/**
* Singleton.java
*/
public class Singleton
{
private static volatile Singleton singleton;
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized (Singleton.class)
{
if (singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0
方式五:静态内部类
Singleton类被装载了,instance不一定被初始化。因为InnerSingleton类没有被主动使用,所以这种方式也具有Lazy loading的效果。
/**
* Singleton.java
*/
public class Singleton
{
private static class InnerSingleton
{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance()
{
return InnerSingleton.INSTANCE;
}
private Singleton(){}
public void sayHello()
{
System.out.println("Hello World!");
}
}
/**
* 验证
*/
public class Demo
{
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
instance.sayHello();
}
}
输出:
Hello World!Process finished with exit code 0