今天看到群里,关于单例模式的多线程下的安全问题:
1:最简单写法的线程安全的单例,但是直接是在内存的,占空间
package com.http.concurrent.test;
public class Singleton {
private static Singleton instance=new Singleton(); // 直接new 可以加上final
private Singleton() { // 私有
}
public static Singleton getInstance() {
return instance;
}
}
2:线程安全下 有问题的:
package com.http.concurrent.test;
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() { // 1
if(instance == null) { // 2
instance=new Singleton(); // 3
}
return instance;
}
}
如果线程B,C,同时访问到了2,;A到3处,new,此时 明显B,C会再次new; 产生多个实例
3:简单的线程安全
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() { // 1
if(instance == null) { // 2
instance=new Singleton(); // 3
}
return instance;
}
}
synchronized 锁的范围太大,效率比较低。
4:缩小synchronized的锁的范围,所称double-check
package com.http.concurrent.test;
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() { // 1
if(instance == null) { // **
synchronized(Singleton.class) {
if(instance == null) { // 2
instance=new Singleton(); // 3
}
}
}
return instance;
}
}
2必须有,不然并发情况下 ,可能会new 多Sinleton;
为了提高效率可以在上面的double-check修改下:
private volatile static Singleton instance;
加上volatile后instance变量就对 所有的线程可见了,有其他线程new一个实例,立马就能看见,**处就可以发现
/**
* 静态内部类 单例模式
*
* @author ljq
* @version 1.0
* @since 1.0
*/
public class Singleton {
private Singleton() {
}
public static final Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 在第一次被引用时被加载
* 由于内部静态类只会被加载一次,故该实现方式时线程安全的
*/
private static class SingletonHolder {
protected static final Singleton instance = new Singleton();
}
public static void main(String[] args) {
Singleton s = Singleton.getInstance();
Singleton s1 = Singleton.getInstance();
System.out.println(s == s1);
}
}
并发容器实现的单例模式:
package xylz.study.concurrency;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ConcurrentDemo1 {
private static final ConcurrentMap<String, ConcurrentDemo1> map = new ConcurrentHashMap<String, ConcurrentDemo1>();
private static ConcurrentDemo1 instance;
public static ConcurrentDemo1 getInstance() {
if (instance == null) {
map.putIfAbsent("INSTANCE", new ConcurrentDemo1());
instance = map.get("INSTANCE");
}
return instance;
}
private ConcurrentDemo1() {
}
}
当然这里只是一个操作的例子,实际上在单例模式文章中有很多的实现和比较。清单2 在存在大量单例的情况下可能有用,实际情况下很少用于单例模式。但是这个方法避免了向Map中的同一个Key提交多个结果的可能,有时候在去掉重复记录上很有用(如果记录的格式比较固定的话)。