单例模式(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方法被调用
到此,单例模式就介绍完了,一般建议大家使用饿汉式单例、静态内部类单例或者双重校验锁来实现单例模式。