好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java 单例模式详细解释

饿汉式

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

/**

  * 饿汉式

  * 类加载到内存后,就是实例化一个单例,JVM保证线程安全

  * 简单使用:推荐使用

  * 唯一缺点:不管用与不用,类加载时就会完成实例化

  */

public class Demo01 {

     //开始先新建一个对象

     private static final Demo01 INSTANCE = new Demo01();

     //构造

     private Demo01(){};

     //调用 getInstance 方法时返回 INSTANCE,唯一创建的对象

     public static Demo01 getInstance(){

         return INSTANCE;

     }

     public static void main(String[] args) {

         Demo01 m1 = Demo01.getInstance();

         Demo01 m2 = Demo01.getInstance();

         //结果为true

         System.out.println(m1 == m2);

     }

}

?

1

2

3

单例模式(饿汉式)优点:饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。

缺点:不管用与不用,类加载时就会完成实例化,会浪费一定的内存空间

改进方法:让对象在使用的时候在进行创建。------>  懒汉式

懒汉式

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

/**

  * 懒汉式

  * 类加载到内存后,就是实例化一个单例,JVM保证线程不安全

  * 唯一缺点:虽然达到了按需的目的,但却带来线程不安全问题

  */

public class Demo02 {

     private static Demo02 INSTANCE ;

     private Demo02(){};

     public static Demo02 getInstance(){

         //判断 INSTANCE 是否为空

        if (INSTANCE == null ){

            try {

                Thread.sleep( 1 );

            } catch (InterruptedException e){

                e.printStackTrace();

            }

            INSTANCE = new Demo02();

        }

        return INSTANCE;

     }

     public static void main(String[] args) {

         for ( int i = 0 ; i < 100 ; i++){

             new Thread(()->

             //输出该对象的hashcode值,通过对比值是否相等来判断是不是唯一的对象

                     System.out.println(Demo02.getInstance().hashCode())

             ).start();

         }

     }

}

?

1

2

3

单例模式(懒汉式)优点:懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。

缺点:懒汉式在多个线程进行访问时有可能会出现多个不同的对象。

改进方法:对创建方法getInstance加锁    ------>   懒汉式(加锁synchronized)

懒汉式(加锁synchronized)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

/**

  * 懒汉式(加锁)

  * 类加载到内存后,就是实例化一个单例,给创建对象的方法的加锁,JVM保证线程安全

  * 唯一缺点:虽然加锁之后可以保证线程是安全的,但会使得整个方法变慢。

  */

public class Demo03 {

     private static Demo03 INSTANCE ;

     private Demo03(){};

     //方法加锁

     public static synchronized Demo03 getInstance(){

         //业务逻辑

         //判断 INSTANCE 是否为空

        if (INSTANCE == null ){

            try {

                Thread.sleep( 1 );

            } catch (InterruptedException e){

                e.printStackTrace();

            }

            INSTANCE = new Demo03();

        }

        return INSTANCE;

     }

     public void m(){

         System.out.println( "m" );

     }

     public static void main(String[] args) {

         for ( int i = 0 ; i < 100 ; i++){

             new Thread(()->

                     System.out.println(Demo03.getInstance().hashCode())

             ).start();

         }

     }

}

?

1

2

3

单例模式(懒汉式(加锁))优点:懒汉式(加锁)可以保证线程的安全性,但是当上锁的方法getInstance中存在业务逻辑代码时,会拉低整个对象创建过程中速度。

缺点:对整个方法加锁,降低了方法运行的时间

改进方法:对创建方法的程序块进行上锁,业务逻辑代码部分不上锁  -------->懒汉式(部分加锁synchronized)

懒汉式(部分加锁synchronized)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

/**

  * 懒汉式(部分加锁)

  * 类加载到内存后,就是实例化一个单例,给创建对象的方法的部分加锁,降低时间

  */

public class Demo04 {

     private static Demo04 INSTANCE ;

     private Demo04(){};

     //方法加锁

     public static Demo04 getInstance(){

         //业务逻辑

         //判断 INSTANCE 是否为空

        if (INSTANCE == null ){

            //对方法的部分代码块进行上锁

            synchronized (Demo04. class ){

                try {

                    Thread.sleep( 1 );

                } catch (InterruptedException e){

                    e.printStackTrace();

                }

                INSTANCE = new Demo04();

            }

        }

        return INSTANCE;

     }

     public void m(){

         System.out.println( "m" );

     }

     public static void main(String[] args) {

         for ( int i = 0 ; i < 100 ; i++){

             new Thread(()->

                     System.out.println(Demo04.getInstance().hashCode())

             ).start();

         }

     }

}

?

1

2

3

单例模式(部分加锁懒汉式)优点:加快了程序的运行,只对创建对象的部分进行加锁

缺点:通过if判断后会有多个线程在等待线程资源,等第一个线程执行完成后还会进行第二个线程创建对象。

改进方法:加入两层if判断可以防止该问题出现 -------->双层检查锁

懒汉式(DCL)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

/**

  * 懒汉式(DCL)

  * Double Check Lock

  */

public class Demo04 {

     private static Demo04 INSTANCE ;

     private Demo04(){};

     //方法加锁

     public static Demo04 getInstance(){

         //业务逻辑

         //判断 INSTANCE 是否为空

        if (INSTANCE == null ){

            //对方法的部分代码块进行上锁

            synchronized (Demo04. class ){

                //再次进行判断,检查 INSTANCE 是否为空

                if (INSTANCE == null ){

                    try {

                        Thread.sleep( 1 );

                    } catch (InterruptedException e){

                        e.printStackTrace();

                    }

                }

                INSTANCE = new Demo04();

            }

        }

        return INSTANCE;

     }

     public void m(){

         System.out.println( "m" );

     }

     public static void main(String[] args) {

         for ( int i = 0 ; i < 100 ; i++){

             new Thread(()->

                     System.out.println(Demo04.getInstance().hashCode())

             ).start();

         }

     }

}

?

1

2

3

单例模式(懒汉式DCL)优点:加快了对象创建的时间,同时保证了线程的安全性。

缺点:当对象发生指令重排时,第二个线程虽然拿到了对象,但是是拿到的不完整的对象,容易出现问题

改进方法:给该方法加上volatile关键字进行上锁可以防止指令重排问题。

延伸一下:为什么要用两层if判断呢?

答:因为使用两层if可以提高方法的运行速度,因为if判断消耗的时间较少,但是synchronized 消耗的时间却很大。在外面加上一层if,可以帮助过滤掉很多线程访问。

懒汉式(DCL)最终版

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

/**

  * 懒汉式(DCL)

  * Double Check Lock

  */

public class Demo04 {

     private static volatile Demo04 INSTANCE ;

     private Demo04(){};

     //方法加锁

     public static Demo04 getInstance(){

         //业务逻辑

         //判断 INSTANCE 是否为空

        if (INSTANCE == null ){

            //对方法的部分代码块进行上锁

            synchronized (Demo04. class ){

                //再次进行判断,检查 INSTANCE 是否为空

                if (INSTANCE == null ){

                    try {

                        Thread.sleep( 1 );

                    } catch (InterruptedException e){

                        e.printStackTrace();

                    }

                }

                INSTANCE = new Demo04();

            }

        }

        return INSTANCE;

     }

     public void m(){

         System.out.println( "m" );

     }

     public static void main(String[] args) {

         for ( int i = 0 ; i < 100 ; i++){

             new Thread(()->

                     System.out.println(Demo04.getInstance().hashCode())

             ).start();

         }

     }

}

对 INSTANCE 进行上锁可以防止指令重排,保证对象的完整性。

延伸:DCL模式为什么要加上volatile ?

答: 我们要从java对象创建过程和CPU乱序执行两个方面考虑。

java对象创建过程可分为:

?

1

2

3

1:内存中分配空间

2:初始化对象

3:变量与对象关联

当发生指令重排是顺序变为

?

1

2

3

1:内存中分配空间

3:变量与对象关联

2:初始化对象

第一个线程访问时,发生指令重排,对象刚创建一半,还未对对象内部的值进行初始化赋值。此时第二个线程进行访问,此时他读取到的就是创建到一半的对象,初始化为空的对象。最终就会导致对象不完整。

静态内部类

加载外部类时不会加载内部类,只有第一次调用getInstance方法时,JVM才加载 Singleton04Holder 并初始化INSTANCE ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public class Demo04 {

     private Demo04 () {

     }

     private static class Demo04Holder {

         private final static Demo04 INSTANCE = new Demo04 ();

     }

     public static Demo04 getInstance() {

         return Demo04Holder.INSTANCE;

     }

     public static void main(String[] args) {

         for ( int i= 0 ; i< 100 ; i++) {

             new Thread(()->{

                 System.out.println(Demo04.getInstance().hashCode());

             }).start();

         }

     }

}

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注的更多内容!

原文链接:https://blog.csdn.net/weixin_43893423/article/details/121031279

查看更多关于Java 单例模式详细解释的详细内容...

  阅读:17次