单例模式核心作用

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


单例模式的优点

由于单例模式只生成一个实例,减少了系统性能的开销,当一个对象的产生需要比较多的资源时(如读取配置、产生其他依赖对象时),则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

单例模式可以在系统设置全局的访问点,优化环共享资源访问。例如可以设计一个单例类,负责所有数据表的映射处理。


常见的5种单例模式实现方式

主要:

  • 饿汉式:线程安全,调用效率高。但是不能延迟加载
  • 懒汉式:线程安全,调用效率不高。但是可以延迟加载

其他:

  • 双重检测锁式:由于JVM底层内部模型原因,偶尔会出问题,不建议使用
  • 静态内部类式:线程安全,调用效率高,可以延迟加载
  • 枚举单例式:线程安全,调用效率高。但是不能延迟加载


单例模式的实现:

  1. 构造方法私有化
  2. 对外提供一个私有的、static修饰的实例
  3. 对外提供一个静态方法,返回实例


饿汉式

  • 单例对象立即加载
  • static 变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发的问题。因此,可以省略 synchronized 关键字。

package shejimoshi.single;

/**
* 饿汉式实现单例模式
*
* @author 陈霓清
*/
public class Single1 {

// 对外提供一个私有的、static修饰的实例。类初始化时,立即加载这个对象。加载类时,天然的是线程安全的
private static Single1 instance = new Single1();

// 构造方法私有化
private Single1() {

}

/**
* 对外提供一个静态方法,返回实例
* @return
*/
public static Single1 getInstance() {
return instance;
}

public static void main(String[] args) {
Single1 s1 = Single1.getInstance();
Single1 s2 = Single1.getInstance();

System.out.println(s1==s2);
}
}


懒汉式

  • 单例对象延迟加载,真正用的时候才加载
  • 资源利用率高了,但是每次调用 getInstance() 方法都要同步,并发效率较低

package shejimoshi.single;

/**
* 懒汉式实现单例模式
*
* @author 陈霓清
*/
public class Single2 {

// 对外提供一个私有的、static修饰的实例
private static Single2 instance;

// 构造方法私有化
private Single2() {

}

/**
* 对外提供一个静态方法,返回实例
* @return
*/
public static synchronized Single2 getInstance() {
if (instance==null) {
instance = new Single2();
}
return instance;
}
}


双重检测锁式

package shejimoshi.single;

/**
* 双重检测锁式实现单例模式
*
* @author 陈霓清
*/
public class Single3 {

// 对外提供一个私有的、static修饰的实例
private static Single3 instance = null;

// 构造方法私有化
private Single3() {

}

/**
* 对外提供一个静态方法,返回实例
* @return
*/
public static Single3 getInstance() {
if (instance==null) {
Single3 temp;
synchronized (Single3.class) {
temp = instance;
if (temp==null) {
synchronized (Single3.class) {
if (temp==null) {
temp = new Single3();
}
}
instance = temp;
}
}
}
return instance;
}
}


静态内部类式

  • 外部类没有 static 属性,不会像饿汉式那样立即加载对象
  • 只有真正调用 getInstance() 方法时,才会调用静态内部类。加载类时是线程安全的,instance 是 static final 类型,保证了内存中只有这样一个实例存在,而且只被赋值一次,从而保证了线程安全性
  • 兼备了并发高效调用和延迟加载的优势

package shejimoshi.single;

/**
* 静态内部类式实现单例模式
*
* @author 陈霓清
*/
public class Single4 {

private Single4() {

}

private static class SingleClassInstance {
private static final Single4 instance = new Single4();
}

public static Single4 getInstance() {
return SingleClassInstance.instance;
}
}


枚举单例式

  • 枚举本身就是单例的。由JVM从根本上提供保障,避免通过反射和反序列化的漏洞
  • 无延迟加载

package shejimoshi.single;

/**
* 枚举单例式实现单例模式
*
* @author 陈霓清
*/
public enum Single5 {

// 定义一个枚举的元素,它就代表是一个实例
INSTANCE;

/**
* 单例可以有自己的操作
* @return
*/
public void singleOpt() {
// 功能处理
}

public static void main(String[] args) {
Single5 s1 = Single5.INSTANCE;
Single5 s2 = Single5.INSTANCE;

System.out.println(s1==s2);
}
}


多线程环境测试

package shejimoshi.single;

import java.util.concurrent.CountDownLatch;

public class Client2 {

public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);

for (int i=0; i<threadNum; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<1000000; i++) {
Object obj = Single1.getInstance();
}
countDownLatch.countDown();
}
}).start();
}

countDownLatch.await();

long end = System.currentTimeMillis();

System.out.println(end-start);
}

}


如何选用

  • 单例对象 占用 资源少,不需要延迟加载
    • 枚举式 好于 饿汉式
  • 单例对象 占用 资源多,需要延迟加载
    • 静态内部类式 好于 懒汉式

回到顶部