主要是划分两种,饱汉和饿汉。
一、饱汉设计
用的时候再创建(就会有线程安全问题需要处理)
三步:
- 私有化构造函数
- 私有化 静态对象
- 创建 静态获取对象方法
public class SingleTon {
private static SingleTon singleTon;
private SingleTon(){};
public static SingleTon getInstance(){
if (singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
改进 - 提高线程安全
添加关键字synchronized,加锁
public class SingleTon {
private static SingleTon singleTon;
private SingleTon(){};
public static synchronized SingleTon getInstance(){
if (singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
再改进 - 锁的细粒度降低
因为使用synchronized 关键会有很大的性能开销
public class SingleTon {
private static SingleTon singleTon;
private SingleTon(){};
public static SingleTon getInstance(){
if (singleTon == null){
synchronized(SingleTon.class){
singleTon = new SingleTon();
}
}
return singleTon;
}
}
还会有一定的问题,多线程访问的情况下,这个锁不能保证内存中对象唯一。当AB线程同时在第8行后,准备进入第9行,此时A拿到锁进去创建一个对象后释放锁,B还能拿着锁进去创建对象
再再改进 - DCL 双重检查锁定
public class SingleTon {
private static SingleTon singleTon;
private SingleTon(){};
public static SingleTon getInstance(){
if (singleTon == null){
synchronized(SingleTon.class){
if(singleTon == null){
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
能在多线程情况下,保持高性能。
但!因为第11行创建对象的操作并不是原子性操作。因此还会有一定的风险
终极改进 volatile 关键字 - 禁止指令重排
因为singleTon= new SingTon()
第一步,划分存储空间
第二步,创建对象填入。
第三步,将对象赋值给引用singleTon
(这三步,是三个指令,多线程打乱顺序,就有可能是导致执行顺序错乱,这就是指令重排)
因此,第一步第二步之间,就给了多线程可趁之机
public class SingleTon {
private static volatile SingleTon singleTon;
private SingleTon(){};
public static SingleTon getInstance(){
if (singleTon == null){
synchronized(SingleTon.class){
if(singleTon == null){
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
二、饿汉设计
没有线程安全的问题(不管用不用,一直都在内存)
public class SingleTon {
private static SingleTon singleTon = new SingleTon();
private SingleTon(){};
public static SingleTon getInstance(){
return singleTon;
}
}
三、如何选择
如果对象不大,且创建不复杂,直接用饿汉方式即可
其他情况则采用懒汉实现方式