单例模式是设计模式的一种, 设计模式就是针对这些场景给出一些经典的解决方案(类似于固定套路).
单例模式能保证某个类在程序中只存在一份实例, 不会创建出多个实例.
举一个生活中的例子, 吃完饭后, 饿汉模式会立刻将所有的碗洗掉(这里的"饿"有着急的意思), 而懒汉模式会先把脏碗放着, 等到下次吃饭的时候再洗, 在生活中, 我们更提倡第一种做法, 但是在计算机中, 懒汉模式更为常用.
//创建饿汉模式
class Singleton{
//使用static创建实例, 并立即对其实例化
// instance即为饿汉模式创建的唯一实例
private static Singleton instance = new Singleton();
//将Singleton类的构造器设为私有, 防止在类的外部产生其他的Singleton实例
private Singleton(){}
//提供get方法, 使外部仅能通过get方法获取到这个实例
public static Singleton getInstance(){
return instance;
}
}
//从外部获取Singleton的唯一实例
public class Demo {
public static void main(String[] args) {
//外部仅能通过这种方法获取到Singleton的实例, 且无法创建其他的实例
Singleton instance = Singleton.getInstance();
}
}
使用饿汉模式, 类的外部就仅能通过getInstance()方法获取到Singleton的这一个实例, 而无法通过new的方式产生其他实例.
//创建懒汉模式
class Singleton{
//类加载时, 先不进行实例化对象
private static Singleton instance = null;
//将构造器设为私有, 防止在类的外部产生其他的Singleton实例
private Singleton(){}
//在第一次获取Singleton对象时, instance为空, 此时进行实例化对象
//之后再试图获取时, instance已不为空, 无法再进行实例化
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
//获取Singleton对象
public class Demo {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
}
}
懒汉模式不会在类加载的过程中直接实例化对象, 而是当用户第一次调用getInstance()方法时再实例化对象, 之后用户再调用getInstance()方法时, 只会直接返回原有的对象, 而不会再次产生新的对象.
上面的两种模式中, 饿汉模式只涉及到了读操作, 并没有涉及对共享数据的修改, 因此饿汉模式是线程安全的, 而懒汉模式即包含了读操作, 又包含了写操作, 并且读和写操作并不具有原子性, 因此懒汉模式是线程不安全的.
要想保证懒汉模式的线程安全, 我们就需要对其进行加锁操作:
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
对上面的懒汉模式进行加锁, 确实可以解决线程安全问题, 但是, 懒汉模式的写操作只发生在instance实例化之前(在instance实例化之后, instance一定不为空, 不会进入if 语句, 也就不涉及写操作), 也就是说, 只要instance被实例化完成, 懒汉模式就不涉及写操作, 也就不涉及线程安全问题了, 但我们的加锁操作是针对全局进行加锁的, 这样多余的操作会产生大量无用的锁竞争操作, 大大减缓了运行效率.
对此, 我们需要在锁的外层再进行一次判定, 使得代码只在instance被实例化之前进行加锁:
//在锁的外面加上一层判断条件
if(instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
注意:这里的两层判断条件看起来是相同的, 但它们执行的效果和时机是完全不同的, 外层判定的是是否要进行加锁, 内层判定的是是否能够创建实例, 并且加锁可能会导致线程产生阻塞, 在两次判定的中间, instance可能会被其他线程所修改.
对于这个问题, 我们只需要对instance加上volatile关键字即可.
private static volatile Singleton instance = null;
总结:要让线程不安全的懒汉模型变为线程安全, 我们需要作出如下调整:
完整的线程安全的懒汉模式代码如下:
class Singleton {
//类加载时, 先不进行实例化对象, 这里记得为instance添加volatile关键字
private static volatile Singleton instance = null;
//将构造器设为私有, 防止在类的外部产生其他的Singleton实例
private Singleton() {
}
//在第一次获取Singleton对象时, instance为空, 此时进行实例化对象
//之后再试图获取时, instance已不为空, 无法再进行实例化
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
//获取Singleton对象
public class Demo2 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
}
}
The end