前言
本章非常重要,通过单例和多线程的结合,在这个过程中会出现很多
以前没有考虑过的情况,一些不良的程序设计如果用在项目中,将可能
遇到非常大的麻烦。本案例也说明了,线程与某些技术相结合时要考虑的
问题很多,本章学习考虑的问题是:
如何使单例模式遇到多线程是安全的,正确的。
一、立即加载/ ‘ 饿汉模式’
立即加载就是使用类的时候已经将对象创建完毕,常见的办法就是new 实例化
从语境看就是“ 捉急”,“ 急迫” 的意思,饿汉模式是在调用之前,实例已经被创建了。
package com.it.po.thread13; public class MyObject { //立即加载 private static MyObject myObject=new MyObject(); private MyObject() {//private修饰就是防止该类再次被实例化 } public static MyObject getMyObject(){ //该方法没有同步,还是存在线程不安全的问题。 return myObject; } }
package com.it.po.thread13; public class MyThread1 extends Thread { @Override public void run() { super.run(); //同一个对象hashCode 得到的值一样 System.out.println(MyObject.getMyObject().hashCode()); } }
package com.it.po.thread13; public class Run1 { public static void main(String[] args) { MyThread1 my1 = new MyThread1(); MyThread1 my2 = new MyThread1(); MyThread1 my3 = new MyThread1(); my1.start(); my2.start(); my3.start(); } }
6723139 6723139 6723139
同一个对象,hashCode一样
二、延迟加载 / ‘ 懒汉模式’
延迟加载就是在调用的时候,实例才会被创建,在调用get方法时new 对象
这种情况就是懒汉模式
package com.it.po.thread13; public class MyObject2 { //延迟加载 private static MyObject2 myObject; private MyObject2() {//private修饰就是防止该类再次被实例化 } public static MyObject2 getMyObject(){ if(myObject==null){ myObject=new MyObject2(); } return myObject; } }
package com.it.po.thread13; public class MyThread2 extends Thread { @Override public void run() { super.run(); //同一个对象hashCode 得到的值一样 System.out.println(MyObject2.getMyObject().hashCode()); } }
package com.it.po.thread13; public class Run2 { public static void main(String[] args) { MyThread2 my1 = new MyThread2(); MyThread2 my2 = new MyThread2(); MyThread2 my3 = new MyThread2(); my1.start(); my2.start(); my3.start(); } }
4419973 4419973 4419973
其实在多线程的情况下,就会出现多个实例的情况。
三、延时加载 / ‘ 懒汉模式’的缺点
看一下懒汉式情况下多线程的出现的问题
package com.it.po.thread13; public class MyObject2 { //延迟加载 private static MyObject2 myObject; private MyObject2() {//private修饰就是防止该类再次被实例化 } public static MyObject2 getMyObject(){ if(myObject==null){ try { System.out.println("线程 "+Thread.currentThread().getName()+" waiting"); Thread.sleep(3000);//模拟在创建对象之前做的一些准备工作 myObject=new MyObject2(); System.out.println("线程 "+Thread.currentThread().getName()+" end "); } catch (InterruptedException e) { e.printStackTrace(); } } return myObject; } }
package com.it.po.thread13; public class MyThread2 extends Thread { @Override public void run() { super.run(); //同一个对象hashCode 得到的值一样 System.out.println(MyObject2.getMyObject().hashCode()); } }
package com.it.po.thread13; public class Run2 { public static void main(String[] args) { MyThread2 my1 = new MyThread2(); MyThread2 my2 = new MyThread2(); MyThread2 my3 = new MyThread2(); my1.start(); my2.start(); my3.start(); } }
线程 Thread-0 waiting 线程 Thread-1 waiting 线程 Thread-2 waiting 线程 Thread-1 end 线程 Thread-0 end 4372721 4372721 线程 Thread-2 end 22840778
错误的单例模式
(一)解决上面的办法
加一个关键字 synchronized ,其他不变
package com.it.po.thread13; public class MyObject2 { //延迟加载 private static MyObject2 myObject; private MyObject2() {//private修饰就是防止该类再次被实例化 } synchronized public static MyObject2 getMyObject(){ if(myObject==null){ try { Thread.sleep(3000);//模拟在创建对象之前做的一些准备工作 myObject=new MyObject2(); } catch (InterruptedException e) { e.printStackTrace(); } } return myObject; } }
16371033 16371033 16371033
此方法效率其实是很低下的,一个线程想得到对象就必须等
上一个线程执行完之后才可以获取对象。
(二)另一个解决办法同步代码块
package com.it.po.thread13; public class MyObject2 { //延迟加载 private static MyObject2 myObject; private MyObject2() {//private修饰就是防止该类再次被实例化 } public static MyObject2 getMyObject(){ if(myObject==null){ try { Thread.sleep(3000);//模拟在创建对象之前做的一些准备工作 synchronized (MyObject2.class) { myObject = new MyObject2(); } } catch (InterruptedException e) { e.printStackTrace(); } } return myObject; } }
4419973 22840778 4372721
此方法只是对关键代码加同步,但是还不是线程安全
如何解决懒汉式遇到的多线程,又顾上效率问题呢?
(三)使用DCL双检查锁机制。(线程安全)
package com.it.po.thread13; public class MyObject3 { //延迟加载 private static MyObject3 myObject; private MyObject3() {//private修饰就是防止该类再次被实例化 } public static MyObject3 getMyObject(){ try { if(myObject!=null) { }else {//为空时 Thread.sleep(3000);//模拟在创建对象之前做的一些准备工作 synchronized (MyObject3.class) { if(myObject==null){ myObject = new MyObject3(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }
package com.it.po.thread13; public class MyThread3 extends Thread { @Override public void run() { super.run(); //同一个对象hashCode 得到的值一样 System.out.println(MyObject3.getMyObject().hashCode()); } }
package com.it.po.thread13; public class Run3 { public static void main(String[] args) { MyThread3 my1 = new MyThread3(); MyThread3 my2 = new MyThread3(); MyThread3 my3 = new MyThread3(); my1.start(); my2.start(); my3.start(); } }
28911017 28911017 28911017
双重检查锁功能,成功解决了懒汉模式遇到的多线程问题
四、使用静态内置类实现单例模式(线程安全)
package com.it.po.thread13; public class MyObject4 { private MyObject4() { } private static class MyObjectHandler{//静态内置类 private static MyObject4 myObject4=new MyObject4(); } public static MyObject4 getInstance(){ return MyObjectHandler.myObject4; } }
package com.it.po.thread13; public class MyThread4 extends Thread { @Override public void run() { super.run(); //同一个对象hashCode 得到的值一样 System.out.println(MyObject4.getInstance().hashCode()); } }
package com.it.po.thread13; public class Run4 { public static void main(String[] args) { MyThread4 my1 = new MyThread4(); MyThread4 my2 = new MyThread4(); MyThread4 my3 = new MyThread4(); my1.start(); my2.start(); my3.start(); } }
6723139 6723139 6723139
五、序列化与反序列化的单例模式
以上静态内置类可以达到线程安全的效果,但是遇到序列化对象时
使用默认的方式运行得到的结果还是多例的。
package com.it.po.thread13; import java.io.Serializable; public class MyObject5 implements Serializable { private static final long serializable=888L; private MyObject5() { } private static class MyObjectHandler{//静态内置类 private static final MyObject5 myObject4=new MyObject5(); } public static MyObject5 getInstance(){ return MyObjectHandler.myObject4; } /* public static MyObject5 readResolve() { System.out.println("调用了readResolve 方法"); return MyObjectHandler.myObject4; }*/ }
package com.it.po.thread13; import java.io.*; public class Run5 { public static void main(String[] args) { try { MyObject5 instance = MyObject5.getInstance(); FileOutputStream fileOutputStream = new FileOutputStream(new File("po.txt")); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(instance); objectOutputStream.close(); fileOutputStream.close(); System.out.println(instance.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e2){ e2.printStackTrace(); } try { FileInputStream fileInputStream = new FileInputStream(new File("po.txt")); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); MyObject5 myObject = (MyObject5) objectInputStream.readObject(); objectInputStream.close(); fileInputStream.close(); System.out.println(myObject.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
19997786 14519747
将MyObject5 放开
运行
14519747 14519747
六、使用static代码块实现多例模式(线程安全)
package com.it.po.thread13; public class MyObject6 { //立即加载 private static MyObject6 myObject=null; private MyObject6() {//private修饰就是防止该类再次被实例化 } static { myObject= new MyObject6(); } public static MyObject6 getMyObject(){ return myObject; } }
package com.it.po.thread13; public class MyThread5 extends Thread { @Override public void run() { super.run(); for(int i=0;i<5;i++){ System.out.println(MyObject6.getMyObject().hashCode()); } } }
package com.it.po.thread13; public class Run6 { public static void main(String[] args) { MyThread5 my1 = new MyThread5(); MyThread5 my2 = new MyThread5(); MyThread5 my3 = new MyThread5(); my1.start(); my2.start(); my3.start(); } }
5585549 5585549 5585549 5585549 5585549 5585549 5585549 5585549 5585549 5585549 5585549 5585549 5585549 5585549
六、使用enum枚举数据类型实现单例模式
和静态代码块相似,使用枚举类时,构造方法会被自动调用
利用这个特性实现单例模式。
具体可以参考设计模式的单例模式部分。
浙公网安备 33010602011771号