单例模式和多线程

单例模式常分为:“懒汉模式”和”饿汉模式“

懒汉模式就是立即加载,指的是在调用方法前实例对象就已经创建完成了

饿汉模式就是延迟加载,指的是在调用方法时实例对象才会被创建

常见的懒汉模式

 1 public class MyObject {
 2     private static MyObject myObject = new MyObject();
 3     private MyObject(){}
 4     /**
 5      *  该版本是立即加载
 6      *  缺点是不能有其他实例变量
 7      *  getInstance方法没有同步
 8      *  可能出现线程不安全问题
 9      * @return
10      */
11     public static MyObject getInstance(){
12         return myObject;
13     }
14 
15     public static void main(String[] args) {
16         Runnable runnable = new Runnable() {
17             @Override
18             public void run() {
19                 System.out.println(com.qf.test02.MyObject.getInstance().hashCode());
20             }
21         };
22         Thread a = new Thread(runnable);
23         a.start();
24         Thread b = new Thread(runnable);
25         b.start();
26     }
27 }
View Code

------------------------------------------------------console------------------------------------------------------

635845204
635845204

实际上getInstance获得的都是同一个对象,实现了立即加载的单例设计模型

常见的懒汉模式

 1 public class MyObject {
 2     private static MyObject myObject ;
 3     private MyObject(){}
 4 
 5     public static MyObject getInstance(){
 6         if (myObject == null) {
 7             myObject = new MyObject();
 8         }
 9         return myObject;
10     }
11 
12     public static void main(String[] args) {
13 
14         Thread t = new Thread(new Runnable() {
15             @Override
16             public void run() {
17                 System.out.println(MyObject.getInstance().hashCode());
18             }
19         });
20         t.start();
21     }
22 }
View Code

------------------------------------------------------console------------------------------------------------------

1700405589

本次虽然是取得一个实例,但是在多线程环境下很可能出现多个实例的情况,与单例模式初衷相背离

模拟出现多个实例的环境:

 1 public class MyObject {
 2     private static MyObject myObject ;
 3     private MyObject(){}
 4 
 5     public static MyObject getInstance(){
 6         try {
 7             if (myObject == null) {
 8                 Thread.sleep(1000);
 9                 myObject = new MyObject();
10             }
11         } catch (InterruptedException e) {
12             e.printStackTrace();
13         }
14         return myObject;
15     }
16 
17     public static void main(String[] args) {
18 
19         for (int i = 0;i < 5;i++) {
20             Thread t = new Thread(new Runnable() {
21                 @Override
22                 public void run() {
23                     System.out.println(MyObject.getInstance().hashCode());
24                 }
25             });
26             t.start();
27         }
28     }
29 }
View Code

------------------------------------------------------console------------------------------------------------------

635845204
2075012821
635845204
100758745
1833680959

出现了不同的hashcode,说明创建出了不同的对象

解决方案1:声明时采用synchronized关键字

 1 public class MyObject {
 2     private static MyObject myObject ;
 3     private MyObject(){}
 4 
 5     public synchronized static MyObject getInstance(){
 6         try {
 7             if (myObject == null) {
 8                 Thread.sleep(1000);
 9                 myObject = new MyObject();
10             }
11         } catch (InterruptedException e) {
12             e.printStackTrace();
13         }
14         return myObject;
15     }
16 
17     public static void main(String[] args) {
18 
19         for (int i = 0;i < 5;i++) {
20             Thread t = new Thread(new Runnable() {
21                 @Override
22                 public void run() {
23                     System.out.println(MyObject.getInstance().hashCode());
24                 }
25             });
26             t.start();
27         }
28     }
29 }
View Code

------------------------------------------------------console------------------------------------------------------

123504074
123504074
123504074
123504074
123504074

得到了相同的实例对象。

但是这种方法采用同步运行,下一个线程想要取得对象必须等上一个线程释放对象,效率比较低

解决方案2:同步代码块1

 1 public class MyObject {
 2     private static MyObject myObject ;
 3     private MyObject(){}
 4 
 5     public static MyObject getInstance(){
 6         try {
 7             synchronized (MyObject.class) {
 8                 if (myObject == null) {
 9                     Thread.sleep(1000);
10                     myObject = new MyObject();
11                 }
12             }
13         } catch (InterruptedException e) {
14             e.printStackTrace();
15         }
16         return myObject;
17     }
18 
19     public static void main(String[] args) {
20 
21         for (int i = 0;i < 5;i++) {
22             Thread t = new Thread(new Runnable() {
23                 @Override
24                 public void run() {
25                     System.out.println(MyObject.getInstance().hashCode());
26                 }
27             });
28             t.start();
29         }
30     }
31 }
View Code

------------------------------------------------------console------------------------------------------------------

1883420913
1883420913
1883420913
1883420913
1883420913

和synchronized声明方法的方式一样都是同步运行的,效率很低

解决方案3:同步代码块,同步部分重要代码

 1 public class MyObject {
 2     private static MyObject myObject ;
 3     private MyObject(){}
 4 
 5     public static MyObject getInstance(){
 6         try {
 7             if (myObject == null) {
 8                 Thread.sleep(1000);
 9                 //有非线程安全问题
10                 synchronized (MyObject.class) {
11                     myObject = new MyObject();
12                 }
13             }
14         } catch (InterruptedException e) {
15             e.printStackTrace();
16         }
17         return myObject;
18     }
19 
20     public static void main(String[] args) {
21 
22         for (int i = 0;i < 5;i++) {
23             Thread t = new Thread(new Runnable() {
24                 @Override
25                 public void run() {
26                     System.out.println(MyObject.getInstance().hashCode());
27                 }
28             });
29             t.start();
30         }
31     }
32 }
View Code

------------------------------------------------------console------------------------------------------------------

100758745
1460486463
635845204
161899259
1833680959

效率得到了提高,但是会出现非线程安全的问题

解决方案4:DCL双锁检查

 1 public class MyObject {
 2     private static MyObject myObject ;
 3     private MyObject(){}
 4 
 5     public static MyObject getInstance(){
 6         try {
 7             if (myObject == null) {
 8                 Thread.sleep(1000);
 9                 //有非线程安全问题
10                 synchronized (MyObject.class) {
11                     if(myObject == null) {
12                         myObject = new MyObject();
13                     }
14                 }
15             }
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         }
19         return myObject;
20     }
21 
22     public static void main(String[] args) {
23 
24         for (int i = 0;i < 5;i++) {
25             Thread t = new Thread(new Runnable() {
26                 @Override
27                 public void run() {
28                     System.out.println(MyObject.getInstance().hashCode());
29                 }
30             });
31             t.start();
32         }
33     }
34 }
View Code

------------------------------------------------------console------------------------------------------------------

1833680959
1833680959
1833680959
1833680959
1833680959

成功解决”懒汉模式“遇到多线程的问题,常用方案

静态内置类实现单例模式

1) 如何保证线程安全: 因为内部的静态类只会被加载一次,只会有一个实例对象,所以是线程安全的

2) 内部类的加载机制: java中的内部类是延时加载的,只有在第一次使用时加载;不使用就不加载

 1 public class MyObject {
 2     private static class MyObjectHandler{
 3         private static MyObject myObject = new MyObject();
 4     }
 5     private MyObject(){}
 6 
 7     public static MyObject getInstance(){
 8         return MyObjectHandler.myObject;
 9     }
10 
11     public static void main(String[] args) {
12         for (int i = 0;i < 5;i++) {
13             Thread t = new Thread(new Runnable() {
14                 @Override
15                 public void run() {
16                     System.out.println(com.qf.test02.MyObject.getInstance().hashCode());
17                 }
18             });
19             t.start();
20         }
21     }
22 }
View Code

------------------------------------------------------console------------------------------------------------------

1654248691
1654248691
1654248691
1654248691
1654248691

序列化与反序列化实现

反序列化中反射会破坏单例模式:

  一般来说, 一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象.不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来,与以前序列化的对象不能equlas

静态内部类可以实现线程安全,但是如果遇到序列化对象时,使用默认方式运行的结果还是多例的

多例环境复现:

 1 public class MyObject implements Serializable {
 2     private static class MyObjectHandler{
 3         private static MyObject myObject = new MyObject();
 4     }
 5     private MyObject(){}
 6 
 7     public static MyObject getInstance(){
 8         return MyObjectHandler.myObject;
 9     }
10 
11     public static void main(String[] args) {
12         try {
13             MyObject myObject = getInstance();
14             FileOutputStream fos = new FileOutputStream(new File("test.txt"));
15             ObjectOutputStream oos = new ObjectOutputStream(fos);
16             oos.writeObject(myObject);
17             oos.close();
18             fos.close();
19             System.out.println(myObject.hashCode());
20         } catch (FileNotFoundException e) {
21             e.printStackTrace();
22         } catch (IOException e) {
23             e.printStackTrace();
24         }
25 
26         try {
27             FileInputStream fis = new FileInputStream(new File("test.txt"));
28             ObjectInputStream ois = new ObjectInputStream(fis);
29             MyObject mm = (MyObject) ois.readObject();
30             ois.close();
31             fis.close();
32             System.out.println(mm.hashCode());
33         } catch (FileNotFoundException e) {
34             e.printStackTrace();
35         } catch (IOException e) {
36             e.printStackTrace();
37         } catch (ClassNotFoundException e) {
38             e.printStackTrace();
39         }
40     }
41 }
View Code

------------------------------------------------------console------------------------------------------------------

21685669
1480010240

解决方案:反序列化时使用readResolve()方法

  序列化操作提供了一个很特别的钩子(hook):类中具有一个被实例化的方法readresolve(),这个方法可以确保类的开发人员在序列化将会返回怎样的object上具有发言权。足够奇怪的,readresolve()并不是静态的,但是在序列化创建实例的时候被引用

 1 public class MyObject implements Serializable {
 2     private static class MyObjectHandler{
 3         private static MyObject myObject = new MyObject();
 4     }
 5     private MyObject(){}
 6 
 7     public static MyObject getInstance(){
 8         return MyObjectHandler.myObject;
 9     }
10 
11     protected Object readResolve(){
12         System.out.println("调用了readResolve方法");
13         return MyObjectHandler.myObject;
14     }
15 
16     public static void main(String[] args) {
17         try {
18             MyObject myObject = getInstance();
19             FileOutputStream fos = new FileOutputStream(new File("test.txt"));
20             ObjectOutputStream oos = new ObjectOutputStream(fos);
21             oos.writeObject(myObject);
22             oos.close();
23             fos.close();
24             System.out.println(myObject.hashCode());
25         } catch (FileNotFoundException e) {
26             e.printStackTrace();
27         } catch (IOException e) {
28             e.printStackTrace();
29         }
30 
31         try {
32             FileInputStream fis = new FileInputStream(new File("test.txt"));
33             ObjectInputStream ois = new ObjectInputStream(fis);
34             MyObject mm = (MyObject) ois.readObject();
35             ois.close();
36             fis.close();
37             System.out.println(mm.hashCode());
38         } catch (FileNotFoundException e) {
39             e.printStackTrace();
40         } catch (IOException e) {
41             e.printStackTrace();
42         } catch (ClassNotFoundException e) {
43             e.printStackTrace();
44         }
45     }
46 }
View Code

------------------------------------------------------console------------------------------------------------------

21685669
调用了readResolve方法
21685669

 static代码块实现单例模式

 1 public class MyObject {
 2     private static MyObject myObject = null;
 3     private MyObject(){}
 4     static {
 5         myObject = new MyObject();
 6     }
 7     public static MyObject getInstance(){
 8         return myObject;
 9     }
10 
11     public static void main(String[] args) {
12         Runnable runnable = new Runnable() {
13             @Override
14             public void run() {
15                 for (int i = 0; i < 5; i++) {
16                     System.out.println(MyObject.getInstance().hashCode());
17                 }
18             }
19         };
20 
21         Thread t1 = new Thread(runnable);
22         Thread t2 = new Thread(runnable);
23         Thread t3 = new Thread(runnable);
24 
25         t1.start();
26         t2.start();
27         t3.start();
28     }
29 }
View Code

------------------------------------------------------console------------------------------------------------------

161899259
161899259
161899259
161899259
161899259
161899259
161899259
161899259
161899259
161899259
161899259
161899259
161899259
161899259
161899259

enum枚举数据类型实现单例模式

枚举和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用

 1 public class MyObject {
 2     private MyObject(){}
 3     public static MyObject getInstance(){
 4         return EnumMyObject.INSTANCE.getInstance();
 5     }
 6     enum EnumMyObject{
 7         INSTANCE;
 8         private MyObject myObject;
 9         private EnumMyObject(){
10             myObject = new MyObject();
11         }
12 
13         public MyObject getInstance(){
14             return myObject;
15         }
16     }
17 
18     public static void main(String[] args) {
19         Runnable runnable = new Runnable() {
20             @Override
21             public void run() {
22                 System.out.println(MyObject.getInstance().hashCode());
23             }
24         };
25         int i = 0;
26         while (i<3) {
27             Thread t1 = new Thread(runnable);
28             Thread t2 = new Thread(runnable);
29             t1.start();
30             t2.start();
31             i++;
32         }
33     }
34 }
View Code

------------------------------------------------------console------------------------------------------------------

2075012821
2075012821
2075012821
2075012821
2075012821
2075012821
原文地址:https://www.cnblogs.com/qf123/p/9767687.html