单例模式,在程序运行期间只有一个实例存在。
简单饿汉式单例模式实现代码如下:
package com.demo;
public class Singleton {
private Singleton() {
}
public static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
空间换时间。
简单懒汉式单例模式代码:
package com.demo;
public class Singleton {
private Singleton() {
}
public static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
时间换空间。
简单的单例模式在多线程中容易出现问题,获取到的instance并非同一个对象。
同步方法实现单例模式:
package com.demo;
public class Singleton {
private Singleton() {
}
public static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
同步方法实现单例模式,效率低下,每一线程进入,都将阻塞其它线程的访问。
双重检查加锁实现单例模式:
package com.demo;
public class Singleton {
private Singleton() {
}
public static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查加锁(Double Checked Locking)很巧妙实现了单例模式,步骤为:
1、检查变量是否初始化(不加锁),如果已经初始化,立即返回变量。
2、获得锁。
3、再次检查变量是否初始化,如果变量已经被之前的某个线程初始化,立即返回此变量。
4、否则的话初始化变量并返回。
遗憾的是,传统的双重检查在java中并不可靠,双重检查锁定的理论是完美的,不幸的是,现实完全不同,java并不能保证它在单处理器或者多处理器计算机上顺利运行。
java平台的内存模型允许“无序写入”,instance的赋值和初始化的顺序是不确定的,在new 一个对象之前instance的值就已经不为null了,所以如果此时有线程执行到if(instance==null)的条件判断的时候就已经返回了一个不为null却未被初始化的instance。
解决方案:1、可以采用“饿汉式”解决,多线程下jvm可以保证类中静态内容只被初始化一次。
2、非要用双重加锁的话,变量前加volatile关键字(jdk5+),告知java虚拟机对加volatile的变量的读写不做优化排序。
package com.demo;
public class Singleton {
private Singleton() {
}
public static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
使用互斥锁,实现双重检查加锁单例模式:
package com.demo;
import java.util.concurrent.locks.ReentrantLock;
public class Singleton {
/**
* 私有构造
*/
private Singleton() {
}
/**
* 互斥锁
*/
private static final ReentrantLock lock = new ReentrantLock();
public static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
/**
* 锁定访问线程,instance需要被修饰成volatile
*/
lock.lock();
try {
if (instance == null) {
instance = new Singleton();
}
} finally {
/**
* 解锁
*/
lock.unlock();
}
}
return instance;
}
}
高级一点的采用读写锁方式:
package com.demo;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 单例模式注册类,此类本身使用饿汉式单例模式,用来管理所有的单例对象
*
* @author kk
*
*/
public class SingletonRegister {
/**
* 私有构造
*/
private SingletonRegister() {
}
/**
* 本身单例的引用
*/
private static final SingletonRegister instance = new SingletonRegister();
/**
* 维护注册信息的map
*/
private static Map<String, Object> map = new HashMap<String, Object>();
/**
* 多线程的可重入的读写锁
*/
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
/**
* 可读锁,可以多个只读锁并存,锁定时可写锁不可用
*/
private static final Lock rLock = rwLock.readLock();
/**
* 可写锁,只能有一个,锁定时其它锁不可用
*/
private static final Lock wLock = rwLock.writeLock();
public static Object getInstance(String className)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Object instance = null;
// 锁定读取锁
rLock.lock();
instance = map.get(className);
try {
if (instance != null) {
return instance;
}
} finally {
// 释放读取锁
rLock.unlock();
}
// 锁定写入的锁
wLock.lock();
try {
instance = map.get(className);
if (instance != null) {
return instance;
}
// 使用反射方式实例化单例对象
instance = Class.forName(className).newInstance();
// 放入map中存储
map.put(className, instance);
} finally {
wLock.unlock();
}
return instance;
}
}
利用虚拟机特性,内部类方式实现单例模式:
package com.demo;
public class Singleton {
/**
* 私有构造
*/
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 内部类
* @author kk
*
*/
public static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
内部类在使用到的时候,才开始加载初始化,由java虚拟机保证了线程安全以及延时加载。
总结:多线程总使用单例模式的建议是不要使用任何双重检查模式,优先考虑饿汉式以及内部类的方式实现单例模式即可,越简单越好;