0
点赞
收藏
分享

微信扫一扫

掌握单例模式,让你的代码更高效、更优雅!


单例模式(Singleton Pattern)是Java中最简单的设计模式之一,属于创建型模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式就是为了使资源能够共享,只需要初始化一次,其它人就能够重复利用,避免一个类频繁地创建和销毁。

使用场景:

1、生产唯一序列号。

2、WEB 中的计数器用单例先缓存起来,不用每次刷新都在数据库里加一次。

3、配置信息config。

核心:构造函数私有化,判断系统是否存在这个实例,如果有则返回存在的实例,如果没有才去创建这个实例,保证整个运行过程实例只有一个。

实现方法:饿汉式、懒汉式、双重校验锁、静态内部类。

一、饿汉式单例模式

它在类加载的时候就初始化,没有达到延迟加载的效果,导致内存浪费,但是避免了多线程的同步问题。

优点:没有加锁,执行效率高,用户使用上比懒汉式更好,绝对的线程安全。

缺点:类加载的时候就初始化,不管实例是否使用,都占着空间。

示例代码:

/**
 * 饿汉式单例,在类加载的时候就初始化
 */
public class HungrySingleton {
 //创建一个静态的HungrySingleton实例
 private static final HungrySingleton hungrySingleton = new HungrySingleton();
 //让构造函数为私有化,这样该类就不会被外部调用而实例化
 private HungrySingleton() {
 }
 public static HungrySingleton getHungrySingletonInstance() {
 return hungrySingleton;
 }
 public void run(){
 System.out.println("HungrySingleton的run方法被调用");
 }
}
/**
 * 测试饿汉式单例模式
 */
public class TestHungrySingleton {
 public static void main(String[] args) {
 HungrySingleton singleton = HungrySingleton
 .getHungrySingletonInstance();
 singleton.run();
 }
}

运行结果:

HungrySingleton的run方法被调用

二、懒汉式单例模式

这种方式产生实例的过程是动态的,默认不实例化,在外部需要使用这个实例的时候才进行实例化,做到了延迟加载,但是也带来了线程安全问题。

1、线程不安全的懒汉式示例:

/**
 * 懒汉式,线程不安全,这种方式在多线程不能正常工作
 */
public class LazyUnSafe {
 // 静态变量,公共内存区域
 private static LazyUnSafe lazy = null;
 private LazyUnSafe() {
 }
 public static LazyUnSafe getInstance() {
 // 如果没有初始化,则进行初始化,否则返回内存里的实例
 if (lazy == null) {
 lazy = new LazyUnSafe();
 }
 return lazy;
 }
 public void run() {
 System.out.println(lazy.toString() + " - LazyUnSafe的run方法被调用");
 }
}
/**
 * 测试懒汉式(线程不安全)的单例模式
 */
public class TestLazyUnSafe {
 public static void main(String[] args) {
 LazyUnSafe lazy = LazyUnSafe.getInstance();
 lazy.run();
 }
}


运行结果:

com.jpm.pattern.singleton.lazy.LazyUnSafe@6a2437ef - LazyUnSafe的run方法被调用

2、验证多线程下的安全问题:

/**
 * 测试懒汉式(线程不安全)的单例模式 说明线程不安全问题
 */
public class TestLazyUnSafeThread {
 public static void main(String[] args) {
 for (int i = 0; i < 6; i++) {
 new Thread(new Runnable() {
 @Override
 public void run() {
 LazyUnSafe lazy = LazyUnSafe.getInstance();
 lazy.run();
 }
 }).start();
 }
 }
}

运行结果:

com.jpm.pattern.singleton.lazy.LazyUnSafe@4f0ab3f2 - LazyUnSafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazyUnSafe@6c89db9a - LazyUnSafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazyUnSafe@6c89db9a - LazyUnSafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazyUnSafe@4f0ab3f2 - LazyUnSafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazyUnSafe@4f0ab3f2 - LazyUnSafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazyUnSafe@4f0ab3f2 - LazyUnSafe的run方法被调用

以上例子说明在多线程环境下,出现了2个不同的实例,所以该代码下的单例模式是线程不安全的,不建议使用。

3、线程安全的懒汉式示例:

/**
 * 懒汉式,线程安全,由于加了synchronized锁,因此效率很低
 */
public class LazySafe {
 private static LazySafe lazySafe = null;
 private LazySafe() {
 }
 /**
  * 在getInstance方法上使用synchronized关键字来保证线程安全
  */
 public static synchronized LazySafe getInstance() {
 if (lazySafe == null) {
 lazySafe = new LazySafe();
 }
 return lazySafe;
 }
 public void run() {
 System.out.println(lazySafe.toString() + " - LazySafe的run方法被调用");
 }
}
/**
 * 测试懒汉式(线程安全)的单例模式,说明线程是安全的
 */
public class TestLazySafeThread {
 public static void main(String[] args) {
 for (int i = 0; i < 6; i++) {
 new Thread(new Runnable() {
 @Override
 public void run() {
 LazySafe lazy = LazySafe.getInstance();
 lazy.run();
 }
 }).start();
 }
 }
}

运行结果:

com.jpm.pattern.singleton.lazy.LazySafe@4eb09321 - LazySafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazySafe@4eb09321 - LazySafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazySafe@4eb09321 - LazySafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazySafe@4eb09321 - LazySafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazySafe@4eb09321 - LazySafe的run方法被调用

com.jpm.pattern.singleton.lazy.LazySafe@4eb09321 - LazySafe的run方法被调用

经过多次模拟,没有发现出现2个实例的情况。

4、下面我们通过一个例子看下这种方式对性能产生的影响。

/**
 * 测试没有加锁的懒汉式性能
 */
public class TestLazyUnSafePerformance {
 public static void main(String[] args) {
 int instanceCount = 99999999;
 long lazyUnSafeStart = System.currentTimeMillis();
 for (int i = 0; i < instanceCount; i++) {
 LazyUnSafe.getInstance();
 }
 long lazyUnSafeEnd = System.currentTimeMillis();
 System.out.println("没有加锁的懒汉式单例创建" + instanceCount + "个实例需要:"
 + (lazyUnSafeEnd - lazyUnSafeStart));
 }
}


运行结果:

没有加锁的懒汉式单例创建99999999个实例需要:6
/**
 * 测试加锁的懒汉式性能
 */
public class TestLazySafePerformance {
 public static void main(String[] args) {
 int instanceCount = 99999999;
 long lazySafeStart = System.currentTimeMillis();
 for (int i = 0; i < instanceCount; i++) {
 LazySafe.getInstance();
 }
 long lazySafeEnd = System.currentTimeMillis();
 System.out.println("加锁的懒汉式单例创建" + instanceCount + "个实例需要:"
 + (lazySafeEnd - lazySafeStart));
 }
}

运行结果:

加锁的懒汉式单例创建99999999个实例需要:649

从两个测试程序执行的时间上来看,生成同样数量的实例,加了锁的懒汉式单例模式需要的时间更多,即加锁后影响了系统的性能。

那问题来了,如果我们的程序既想实现懒加载(什么时候用,什么时候初始化),又想保证线程安全,还要保证系统性能的时候该怎么办呢?

5、下面介绍一种“双重校验锁”的单例模式

双重校验锁,又叫双检锁(DCL,即 double-checked locking),这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

/**
 * 测试双重校验锁的单例模式性能
 */
public class TestLazyDclPerformance {
 public static void main(String[] args) {
 int instanceCount = 99999999;
 long lazyDclStart = System.currentTimeMillis();
 for (int i = 0; i < instanceCount; i++) {
 LazyDcl.getInstance();
 }
 long lazyDclEnd = System.currentTimeMillis();
 System.out.println("加锁的懒汉式单例创建" + instanceCount + "个实例需要:"
 + (lazyDclEnd - lazyDclStart));
 }
}

运行结果:

加锁的懒汉式单例创建99999999个实例需要:7

可以看出,采用双检锁的单例模式,花费的时间和不加锁的懒加载单例模式差不多,所以说在多线程情况下能保持高性能可以采用双检锁的单例模式。

三、静态内部类实现的单例模式

/**
 * 静态内部类实现的单例模式 特点:内部类默认不加载,在外部类被调用的时候内部类才会被加载,这种方式的单例即避免了内存浪费又不会带来性能问题
 */
public class InnerSingleton {
 private InnerSingleton() {
 }
 /**
  * 内部类默认不加载,在外部类InnerSingleton被调用的时候内部类singleton才会被加载
  */
 private static class singleton {
 private static final InnerSingleton instance = new InnerSingleton();
 }
 /**
  * final保证这个方法不会被覆写或重载
  */
 public static final InnerSingleton getInstance() {
 return singleton.instance;
 }
 public void run() {
 System.out.println(singleton.instance.toString() + " - InnerSingleton的run方法被调用");
 }
}
/**
 * 测试静态内部类实现的单例模式
 */
public class TestInnerSingleton {
 public static void main(String[] args) {
 InnerSingleton instance = InnerSingleton.getInstance();
 instance.run();
 }
}

运行结果:

com.jpm.pattern.singleton.innerclass.InnerSingleton@3a5476a7 - InnerSingleton的run方法被调用

到此,单例模式就介绍完了,一般建议大家使用饿汉式单例、静态内部类单例或者双重校验锁来实现单例模式。

举报

相关推荐

0 条评论