Java单利模式与多线程总结归纳
概念:
java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。
单例模式有一下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
这里主要详细介绍两种:懒汉式和饿汉式
一、立即加载/饿汉式
在调用方法前,实例就已经被创建,代码:
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyObject{ //立即加载方式==恶汉模式 privatestaticMyObjectmyObject=newMyObject(); privateMyObject(){ } publicstaticMyObjectgetInstance(){ //此代码版本为立即加载 //此版本代码的缺点是不能有其他实例变量 //因为getInstance()方法没有同步 //所以有可能出现非线程安全的问题 returnmyObject; } }
创建线程类
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyThreadextendsThread{ @Override publicvoidrun(){ System.out.println(MyObject.getInstance().hashCode()); } }
创建运行类
packagecom.weishiyao.learn.day.singleton.ep; publicclassRun{ publicstaticvoidmain(String[]args){ MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); t.start(); t.start(); t.start(); } }
运行结果
1167772895
2167772895
3167772895
hashCode是同一个值,说明对象也是同一个,说明实现了立即加载型的单利模式
二、延迟加载/懒汉式
在调用方法以后实例才会被创建,实现方案可以是将实例化放到无参构造函数当中,这样只有当调用的时候才会创建对象的实例,代码:
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyObject{ privatestaticMyObjectmyObject; privateMyObject(){ } publicstaticMyObjectgetInstance(){ //延迟加载 if(myObject!=null){ }else{ myObject=newMyObject(); } returnmyObject; } }
创建线程类
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyThreadextendsThread{ @Override publicvoidrun(){ System.out.println(MyObject.getInstance().hashCode()); } }
创建运行类
packagecom.weishiyao.learn.day8.singleton.ep2; publicclassRun{ publicstaticvoidmain(String[]args){ MyThreadt1=newMyThread(); t1.start(); } }
运行结果
1167772895
这样虽然取出了一个对象的实例,但是如果在多线程的环境中,就会出现多个实例的情况,这样就不是单例模式了
运行测试类
packagecom.weishiyao.learn.day.singleton.ep; publicclassRun{ publicstaticvoidmain(String[]args){ MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); t.start(); t.start(); t.start(); t.start(); t.start(); } }
运行结果
1980258163
21224717057
31851889404
4188820504
51672864109
既然出现问题,就要解决问题,在懒汉模式中的多线程的解决方案,代码:
第一种方案,最常见的,加synchronized,而synchronized可以加到不同的位置
第一种,方法锁
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyObject{ privatestaticMyObjectmyObject; privateMyObject(){ } synchronizedpublicstaticMyObjectgetInstance(){ //延迟加载 try{ if(myObject!=null){ }else{ //模拟在创建对象之前做一些准备性的工作 Thread.sleep();myObject=newMyObject();} }catch(InterruptedExceptione){ e.printStackTrace(); } returnmyObject; } }
这种synchronized的同步方案导致效率过于低下,整个方法都被锁住
第二种synchronized使用方案
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyObject{ privatestaticMyObjectmyObject; privateMyObject(){ } publicstaticMyObjectgetInstance(){ //延迟加载 try{ synchronized(MyObject.class){ if(myObject!=null){ }else{ //模拟在创建对象之前做一些准备性的工作 Thread.sleep(); myObject=newMyObject(); } } }catch(InterruptedExceptione){ e.printStackTrace(); } returnmyObject; } }
这种方法效率一样很低,方法内的所有代码都被锁住,只需要锁住关键代码就好,第三种synchronized使用方案
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyObject{ privatestaticMyObjectmyObject; privateMyObject(){ } publicstaticMyObjectgetInstance(){ //延迟加载 try{ if(myObject!=null){ }else{ //模拟在创建对象之前做一些准备性的工作 Thread.sleep(); synchronized(MyObject.class){ myObject=newMyObject(); } } }catch(InterruptedExceptione){ e.printStackTrace(); } returnmyObject; } }
这么写看似是最优方案了,但是,运行一下结果,发现,其实它是非线程安全的
结果:
11224717057
2971173439
31851889404
41224717057
51672864109
Why?
虽然锁住了对象创建的语句,每次只能有一个线程完成创建,但是,当第一个线程进来创建完成Object对象以后,第二个线程进来还是可以继续创建的,因为我们紧紧只锁住了创建语句,这个问题解决方案
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyObject{ privatestaticMyObjectmyObject; privateMyObject(){ } publicstaticMyObjectgetInstance(){ //延迟加载 try{ if(myObject!=null){ }else{ //模拟在创建对象之前做一些准备性的工作 Thread.sleep(); synchronized(MyObject.class){ if(myObject==null){ myObject=newMyObject(); } } } }catch(InterruptedExceptione){ e.printStackTrace(); } returnmyObject; } }
只需要在锁里面再添加一个判断,就可以保证单例了,这个是DCL双检查机制
结果如下:
11224717057
21224717057
31224717057
41224717057
51224717057
三、使用内置静态类实现单例
主要代码
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyObject{ //内部类方式 privatestaticclassMyObjectHandler{ privatestaticMyObjectmyObject=newMyObject(); } publicMyObject(){ } publicstaticMyObjectgetInstance(){ returnMyObjectHandler.myObject; } }
线程类代码
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyThreadextendsThread{ @Override publicvoidrun(){ System.out.println(MyObject.getInstance().hashCode()); } }
运行类
packagecom.weishiyao.learn.day.singleton.ep; publicclassRun{ publicstaticvoidmain(String[]args){ MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); t.start(); t.start(); t.start(); t.start(); t.start(); } }
结果
1851889404
1851889404
1851889404
1851889404
1851889404
通过内部静态类,得到了线程安全的单例模式
四、序列化和反序列化单例模式
内置静态类可以达到线程安全的问题,但如果遇到序列化对象时,使用默认方式得到的结果还是多例的
MyObject代码
packagecom.weishiyao.learn.day8.singleton.ep5; importjava.io.Serializable; publicclassMyObjectimplementsSerializable{ /** * */ privatestaticfinallongserialVersionUID=888L; //内部类方式 privatestaticclassMyObjectHandler{ privatestaticMyObjectmyObject=newMyObject(); } publicMyObject(){ } publicstaticMyObjectgetInstance(){ returnMyObjectHandler.myObject; } //protectedMyObjectreadResolve(){ //System.out.println("调用了readResolve方法!"); //returnMyObjectHandler.myObject; //} }
业务类
packagecom.weishiyao.learn.day.singleton.ep; importjava.io.File; importjava.io.FileInputStream; importjava.io.FileNotFoundException; importjava.io.FileOutputStream; importjava.io.IOException; importjava.io.ObjectInputStream; importjava.io.ObjectOutputStream; publicclassSaveAndRead{ publicstaticvoidmain(String[]args){ try{ MyObjectmyObject=MyObject.getInstance(); FileOutputStreamfosRef=newFileOutputStream(newFile("myObjectFile.txt")); ObjectOutputStreamoosRef=newObjectOutputStream(fosRef); oosRef.writeObject(myObject); oosRef.close(); fosRef.close(); System.out.println(myObject.hashCode()); }catch(FileNotFoundExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); } FileInputStreamfisRef; try{ fisRef=newFileInputStream(newFile("myObjectFile.txt")); ObjectInputStreamiosRef=newObjectInputStream(fisRef); MyObjectmyObject=(MyObject)iosRef.readObject(); iosRef.close(); fisRef.close(); System.out.println(myObject.hashCode()); }catch(FileNotFoundExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); }catch(ClassNotFoundExceptione){ e.printStackTrace(); } } }
结果
1970928725
21099149023
两个不同的hashCode,证明并不是同一个对象,解决方案,添加下面这段代码
protectedMyObjectreadResolve(){ System.out.println("调用了readResolve方法!"); returnMyObjectHandler.myObject; }
在反序列化的时候调用,可以得到同一个对象
System.out.println(myObject.readResolve().hashCode());
结果
11255301379
2调用了readResolve方法!
31255301379
相同的hashCode,证明得到了同一个对象
五、使用static代码块实现单例
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码快这个特性来实现单利模式
MyObject类
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyObject{ privatestaticMyObjectinstance=null; privateMyObject(){ super(); } static{ instance=newMyObject(); } publicstaticMyObjectgetInstance(){ returninstance; } }
线程类
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyThreadextendsThread{ @Override publicvoidrun(){ for(inti=;i<;i++){ System.out.println(MyObject.getInstance().hashCode()); } } }
运行类
packagecom.weishiyao.learn.day.singleton.ep; publicclassRun{ publicstaticvoidmain(String[]args){ MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); t.start(); t.start(); t.start(); t.start(); t.start(); } }
运行结果:
11678885403
21678885403
31678885403
41678885403
51678885403
61678885403
71678885403
81678885403
91678885403
101678885403
111678885403
121678885403
131678885403
141678885403
151678885403
161678885403
171678885403
181678885403
191678885403
201678885403
211678885403
221678885403
231678885403
241678885403
251678885403
通过静态代码块只执行一次的特性也成功的得到了线程安全的单例模式
六、使用enum枚举数据类型实现单例模式
枚举enum和静态代码块的特性类似,在使用枚举时,构造方法会被自动调用,也可以用来实现单例模式
MyObject类
packagecom.weishiyao.learn.day.singleton.ep; importjava.sql.Connection; importjava.sql.DriverManager; importjava.sql.SQLException; publicenumMyObject{ connectionFactory; privateConnectionconnection; privateMyObject(){ try{ System.out.println("调用了MyObject的构造"); Stringurl="jdbc:mysql://...:/wechat_?useUnicode=true&characterEncoding=UTF-"; Stringname="root"; Stringpassword=""; StringdriverName="com.mysql.jdbc.Driver"; Class.forName(driverName); connection=DriverManager.getConnection(url,name,password); }catch(ClassNotFoundExceptione){ e.printStackTrace(); }catch(SQLExceptione){ e.printStackTrace(); } } publicConnectiongetConnection(){ returnconnection; } }
线程类
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyThreadextendsThread{ @Override publicvoidrun(){ for(inti=;i<;i++){ System.out.println(MyObject.connectionFactory.getConnection().hashCode()); } } }
运行类
packagecom.weishiyao.learn.day.singleton.ep; publicclassRun{ publicstaticvoidmain(String[]args){ MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); MyThreadt=newMyThread(); t.start(); t.start(); t.start(); t.start(); t.start(); } }
运行结果
1调用了MyObject的构造
256823666
356823666
456823666
556823666
656823666
756823666
856823666
956823666
1056823666
1156823666
1256823666
1356823666
1456823666
1556823666
1656823666
1756823666
1856823666
1956823666
2056823666
2156823666
2256823666
2356823666
2456823666
2556823666
2656823666
上面这种写法将枚举类暴露了,违反了“职责单一原则”,可以使用一个类将枚举包裹起来
packagecom.weishiyao.learn.day.singleton.ep; importjava.sql.Connection; importjava.sql.DriverManager; importjava.sql.SQLException; publicclassMyObject{ publicenumMyEnumSingleton{ connectionFactory; privateConnectionconnection; privateMyEnumSingleton(){ try{ System.out.println("调用了MyObject的构造"); Stringurl="jdbc:mysql://...:/wechat_?useUnicode=true&characterEncoding=UTF-"; Stringname="root"; Stringpassword=""; StringdriverName="com.mysql.jdbc.Driver"; Class.forName(driverName); connection=DriverManager.getConnection(url,name,password); }catch(ClassNotFoundExceptione){ e.printStackTrace(); }catch(SQLExceptione){ e.printStackTrace(); } } publicConnectiongetConnection(){ returnconnection; } } publicstaticConnectiongetConnection(){ returnMyEnumSingleton.connectionFactory.getConnection(); } }
更改线程代码
packagecom.weishiyao.learn.day.singleton.ep; publicclassMyThreadextendsThread{ @Override publicvoidrun(){ for(inti=;i<;i++){ System.out.println(MyObject.getConnection().hashCode()); } } }
结果
1调用了MyObject的构造
21948356121
31948356121
41948356121
51948356121
61948356121
71948356121
81948356121
91948356121
101948356121
111948356121
121948356121
131948356121
141948356121
151948356121
161948356121
171948356121
181948356121
191948356121
201948356121
211948356121
221948356121
231948356121
241948356121
251948356121
261948356121
以上总结了单利模式与多线程结合时遇到的各种情况和解决方案,以供以后使用时查阅。