单例模式:
定义:某个类在某个系统中只能有一个实例化对象被获取和使用
实现要点: 1.构造器私有
2.含有一个该类的静态变量保存这个唯一实例
3.对外提供获取该实例对象的方式
分类:1.饿汉式 2.懒汉式
下面对这两种单例模式展开分析,进行对比
饿汉式
1.代码实现1(静态常量方式实现)
public class SingleTon{
private static final SingleTon singleTon = new SingleTon();
private SingleTon(){}
public static SingleTon getSingleTon(){
return singleTon;
}
}
2.代码实现2(静态代码块方式实现)
`public class SingleTon{
private staic SingleTon;
//在代码块执行时,创建单例对象
static{singleTon = new SingleTon();}
private SingleTon(){}
public static SingleTon getSingleTon(){
return singleTon;}
}`
public class TestSingleTon{ public static void main(Stirng[] args){ SingleTon s1 = SindleTon.getSingleTon; SingleTon s2 = SindleTon.getSingleTon; System.out.println(s1 == s2); //true } }
3.特点及代码分析
1)私有化构造器
2)在类的内部创建一个类的实例,static
3)私有化对象,通过公共方法调用
4)此方法只能通过类来调用,因为是static的,类的实例也是static的
5)只创建一个对象,不会有线程不安全的情况
6)在类加载是就完成了类实例化,避免了线程同步问题,但没有达到Lazy Loading的效果,如果没有使用过这个对象,会造成内存浪费
7)如何避免这种情况发生呢?可以采用枚举形式实现单例模式
代码块如下:
public enum SingleTon{ INSTANCE; }
懒汉式
1.代码实现1(线程不安全方式)
public class SingleTon{ private SingleTon(){} private static SingleTon singleTon = null; //当调用方法时,才创建单例对象 public static SingleTon getSingleTon(){ if(singleTon == null){ singleTon = new SingleTon(); } return singleTon; } }
2.代码分析1
1)起到了lazy Loading的作用,即延迟加载对象,但只能在单线程时使用
2)如果早多个线程下,一个线程进入了if(singleTon == null)判断语句时,若满足条件判断且还没来得及继续执行,另一个线程也进入到if(singleTon == null)判断语句,这就会产生多个实例对象,即线程不安全。
3.代码实现2(线程安全方式\双重检查)
public class SingleTon{ private SingleTon(){} private static SingleTon singleTon = null; //当调用方法时,才创建单例对象 public static SingleTon getSingleTon(){ if(singleTon == null){//第一层检查,检查是否有引用对象,如果一个线程获取了实例,则不需要进入同步代码块中了 synchronized (SingleTon.class){//第一层锁,保证只有一个线程进入。同步代码块使用的锁是单例的字节码文件对象,且只能用这个锁 if(singleTon == null){ //第二层检查 singleTon = new SingleTon(); } } return singleTon; } }
4.代码分析2
//volatile关键字的作用为禁止指令重排,保证返回singleTon对象一定在创建对象后
singleTon = new SingleTon();该语句的底层实现逻辑为:
(1)在堆上开辟空间
(2)属性初始化
(3)引用指向对象
//假设以上三个内容为三条单独指令,因指令重排可能会导致执行顺序为1->3->2(正常为1->2->3),当单例模式中存在普通变量需要在构造方法中进行初始化操作时,单线程情况下,顺序重排没有影响;但在多线程情况下,假如线程1执行singleton=new Singleton()语句时先1再3,由于系统调度线程2的原因没来得及执行步骤2,但此时已有引用指向对象也就是singleton!=null,故线程2在第一次检查时不满足条件直接返回singleton,此时singleton为null(即str值为null)
//volatile关键字可保证singleton=new Singleton()语句执行顺序为123,因其为非原子性依旧可能存在系统调度问题(即执行步骤时被打断),但能确保的是只要singleton!=0,就表明一定执行了属性初始化操作;而若在步骤3之前被打断,其他线程可进入第一层检查向下执行创建对象.此时线程2拿到的不是一个null singleton,而是一个没有被步骤2正确初始化的singleton。
5.代码实现3(静态内部类)
//懒汉式:静态内部类形式
public class SingleTon { private SingleTon(){ } private static class Inner{ private static final SingleTon SINGLE_TON = new SingleTon(); } public static SingleTon getSingleTon(){ return Inner.SINGLE_TON; } }
//分析:(1)只有在调用方法时,才会加载到内部类,从而完成类的实例化,singleTon。
(2)避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
6.实际应用
`public class LazySingleDesign {
private static LazySingleDesign lazySingleDesign = null ;
private LazySingleDesign(){}
public static LazySingleDesign getInstance(){
synchronized(LazySingleDesign.class){
if(lazySingleDesign == null){
lazySingleDesign = new LazySingleDesign();
}
}
return lazySingleDesign;
}
}`
@Test public void test() { ExecutorService executor = Executors.newFixedThreadPool(10); for(int i = 0 ; i < 10;i++){ executor.execute(new Runnable() { @Override public void run() { LazySingleDesign user = LazySingleDesign.getInstance(); System.out.println("design = " + user); } }); } }
getInstance()方法内的第一个if判断可以去掉,生成的也是单例。另外可见性可以去掉也不影响生成的单例。
参考文章链接:1.https://blog.csdn.net/weixin_42617262/article/details/90448083
2.https://big-data.blog.csdn.net/article/details/83422780?spm=1001.2101.3001.6650.9&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-9-83422780-blog-115265060.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-9-83422780-blog-115265060.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=16
3.https://blog.csdn.net/qq_42804736/article/details/115265060