设计模式之单例模式(singleton)
单例模式
特点
- 构造器需要私有化
- 提供访问点
优点
节省内存,严格控制访问
缺点
难以扩展,严格意义上讲不符合开闭原则
饿汉模式
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
public static void main(String[] args) {
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(instance);
}
}
缺点 : 可以通过序列化破坏单例
解决 : 可以增加readResoResolve()覆盖流处理,来保证单例
懒汉模式
线程不安全
public class UnSafeSingleton {
private static UnSafeSingleton unSafeSingleton;
private UnSafeSingleton(){}
public static UnSafeSingleton getInstance(){
if(null == unSafeSingleton){
unSafeSingleton = new UnSafeSingleton();
}
return unSafeSingleton;
}
public static void main(String[] args) {
System.out.println(UnSafeSingleton.getInstance());
}
}
不可以在多线程环境下使用,会破坏单例
线程安全
synchronized
public class SafeSingleton {
private static SafeSingleton safeSingleton;
private SafeSingleton(){}
public static synchronized SafeSingleton getInstance(){
if(null == safeSingleton){
safeSingleton = new SafeSingleton();
}
return safeSingleton;
}
public static void main(String[] args) {
System.out.println(SafeSingleton.getInstance());
}
}
static+synchronized会锁住整个class对象,性能低下
double-check
public class DoubleCheckSingleton {
private static DoubleCheckSingleton doubleCheckSingleton;
private DoubleCheckSingleton(){}
public static DoubleCheckSingleton getInstance(){
if(null == doubleCheckSingleton){
synchronized (doubleCheckSingleton){
if(null == doubleCheckSingleton){
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
public static void main(String[] args) {
System.out.println(DoubleCheckSingleton.getInstance());
}
}
性能稍微提升,但是还是不高
内部类
public class InnerClassSingleton {
private InnerClassSingleton(){}
private static class Holder{
static InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
}
private static InnerClassSingleton getInstance(){
return Holder.innerClassSingleton;
}
public static void main(String[] args) {
System.out.println(InnerClassSingleton.getInstance());
}
}
缺点 : 可以通过反射破坏单例
枚举式单例
public class EnumSingleton {
private EnumSingleton(){}
private enum Holder{
INSTANCE;
private EnumSingleton enumSingleton;
Holder(){
this.enumSingleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return this.enumSingleton;
}
}
public static void main(String[] args) {
System.out.println(Holder.INSTANCE.getInstance());
}
}
- 这种方式推荐,首先他性能比较好,在序列化时期,jdk本身保证枚举是单例的.
- 这种基于饿汉+懒汉的方式实现
容器单例
线程不安全
public class ContainerSingleton {
private ContainerSingleton(){}
static Map<String,Object> container = new ConcurrentHashMap<>();
public static Object getInstance(String className){
if(!container.containsKey(className)){
try {
container.put(className,Class.forName(className).newInstance());
}catch (Exception e){
e.printStackTrace();
}
}
return container.get(className);
}
public static void main(String[] args) {
Object instance = ContainerSingleton.getInstance(
"com.blaaair.singleton.ContainerSingleton");
Object instance2 = ContainerSingleton.getInstance(
"com.blaaair.singleton.ContainerSingleton");
System.out.println(instance);
System.out.println(instance2);
}
}
线程安全
public class ContainerSingleton {
private ContainerSingleton(){}
static Map<String,Object> container = new ConcurrentHashMap<>();
public static Object getInstance(String className){
synchronized (container){
if(!container.containsKey(className)){
try {
container.put(className,Class.forName(className).newInstance());
}catch (Exception e){
e.printStackTrace();
}
}
}
return container.get(className);
}
public static void main(String[] args) {
Object instance = ContainerSingleton.getInstance(
"com.blaaair.singleton.ContainerSingleton");
Object instance2 = ContainerSingleton.getInstance(
"com.blaaair.singleton.ContainerSingleton");
System.out.println(instance);
System.out.println(instance2);
}
}
在方法里增加synchronized(){}静态块 ,这里也是spring内部的方式.
ConcurrentHashMap
其实也解决不了此处的线程安全问题,因为它只能保证ConcurrentHashMap内部的方法线程安全,但是对于getInstance方法来说还是不安全的
线程间单例(基于ThreadLocal)
不能保证程序全局唯一,但能保证线程唯一
public class ThreadLocalSingleton {
private ThreadLocalSingleton() { }
private static ThreadLocal<ThreadLocalSingleton> singletonThreadLocal
= new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
public static ThreadLocalSingleton getInstance(){
return singletonThreadLocal.get();
}
public static void main(String[] args) {
System.out.printf("ThreadName - %s : "+ThreadLocalSingleton.getInstance()+"\n",Thread.currentThread().getName());
new Thread(new MyRunner()).start();
new Thread(new MyRunner()).start();
}
}
class MyRunner implements Runnable{
@Override
public void run() {
System.out.printf("ThreadName - %s : "+ThreadLocalSingleton.getInstance()+"\n",Thread.currentThread().getName());
System.out.printf("ThreadName - %s : "+ThreadLocalSingleton.getInstance()+"\n",Thread.currentThread().getName());
System.out.printf("ThreadName - %s : "+ThreadLocalSingleton.getInstance()+"\n",Thread.currentThread().getName());
}
}
输出
ThreadName - main : com.blaaair.singleton.ThreadLocalSingleton@1b6d3586
ThreadName - Thread-0 : com.blaaair.singleton.ThreadLocalSingleton@43f47252
ThreadName - Thread-1 : com.blaaair.singleton.ThreadLocalSingleton@730b7ffd
ThreadName - Thread-0 : com.blaaair.singleton.ThreadLocalSingleton@43f47252
ThreadName - Thread-1 : com.blaaair.singleton.ThreadLocalSingleton@730b7ffd
ThreadName - Thread-0 : com.blaaair.singleton.ThreadLocalSingleton@43f47252
ThreadName - Thread-1 : com.blaaair.singleton.ThreadLocalSingleton@730b7ffd
每个线程有一个单例对象,所以跨线程间是线程不安全的
原理
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
在ThreadLocal源码中,维护一个ThreadLocalMap,当setInitialValue()或者set()时,
map.set(this, value);
会把当前线程设置为key,值设置成value
所以此处也证明基于ThreadLocal的单例也是容器式单例
应用场景 : DynamicDataSource数据源
设计模式之单例模式(singleton)
https://www.blaaair.com/archives/she-ji-mo-shi-zhi-dan-li-mo-shi-singleton