0.学习要点
- 简单了解单例模式
- 如何使单例模式遇到多线程是安全的、正确的。
1.立即加载/“饿汉模式”
- 单例模式:
一个类只有一个对象实例。 - 关于立即加载/饿汉模式:
立即加载就是使用类的时候已经将对象创建完毕了。在调用方法前,实例已经被创建了。 - 测试代码:
单例类:
在线程类中使用public class Object6_02 { //立即加载/饿汉模式 private static Object6_02 obj=new Object6_02(); //提前加载 private Object6_02() { } public static Object6_02 getInstance(){ //缺点是不能有其他实例变量,因为getInstance方法没有同步,可能会出现非线程安全 return obj; } }
Object6_02.getInstance().hashCode()
输出对象的hashCode。
在测试类中实例化多个线程对象,并start(),得到的结果是同一个hashCode,说明是只有一个对象。
2.延迟加载/“懒汉模式”简介及其缺点
- 延时加载/懒汉模式:
在调用get方法时实例才被创建,常见的实现方法是在get()中new实例化。 延迟加载(会出错):
public class Object6_03 { //延迟加载/懒汉模式解析 public static Object6_03 obj; public Object6_03() { } public static Object6_03 getInstance(){ //延迟加载 if(obj!=null){ }else{ obj=new Object6_03(); } return obj; } }
在多线程环境下使用
Object6_02.getInstance().hashCode()
创建对象并打印hashCode会出现非线程安全,会取出多个实例对象。
实测在public static Object6_03 obj;
前加volatile关键字无法解决问题。
3.延迟加载/懒汉模式缺点的三种解决方法
- 声明synchronize关键字:
不足:效率非常低下,同步运行,下一个线程想要取得对象,必须等上一个线程释放锁之后才可以执行public class Object6_05 { //延迟加载/懒汉模式的缺点的解决方法1:声明synchronize关键字 private static Object6_05 obj; public Object6_05() { } //声明synchronize关键字 synchronized public static Object6_05 getInstance(){ try{ if(obj!=null){ }else{ //模拟创建对象之前做一些准备工作 Thread.sleep(3000); obj=new Object6_05(); } }catch (InterruptedException e) { e.printStackTrace(); } return obj; } }
- synchronized同步代码块:
将getInstance方法更改如下:
但是仍然会有三秒的开支。public static Object6_05 getInstance(){ try{ synchronized (Object6_05.class) { if(obj!=null){ }else{ Thread.sleep(3000); //创建实例化准备工作 obj=new Object6_05(); } } }catch (InterruptedException e) { e.printStackTrace(); } return obj; }
- 针对某些重要代码进行同步:
但是遇到多线程还是会出现多个对象。没有解决根本问题。if(obj!=null){ }else{ Thread.sleep(3000); synchronized (Object6_05.class) { obj=new Object6_05(); } }
最终方案:DCL双检查锁机制
双检查锁就是在同步代码块调用之前检查一遍,再在同步代码块内部再检查一遍。
代码如下:public class Object6_08 { //延迟加载/懒汉模式的缺点的解决方法4:DCL双检查锁机制(Double-Check Locking) private volatile static Object6_08 obj; private Object6_08() { } public static Object6_08 getInstance() { try{ if(obj!=null){ //一重检查 }else{ Thread.sleep(3000); synchronized (Object6_08.class) { //一重锁 if(obj==null){ //二重检查 obj=new Object6_08(); } } } }catch (InterruptedException e) { e.printStackTrace(); } return obj; } }
注意一定要使用volatile关键字,否则会出现各种各样的问题。
4.使用其他方法实现安全的单例模式
4.1 使用静态内置类实现单例模式
public class Object{
//使用静态内置类实现单例模式
//静态内部类ObjectHandler
private static class ObjectHandler{
private static Object obj=new Object();
}
public Object() {
}
public static Object getInstance(){
return ObjectHandler.obj;
}
}
4.2 序列化与反序列化时的单例模式实现
序列化:将对象的状态信息转换为可以存储或传输的形式的过程。
4.1的代码在遇到序列化的对象时仍会出现不同对象。输出与读取的对象不同。
解决方法:
在反序列化中加上readResolve()方法。
单例类代码:
public class Object6_10 implements Serializable{
//序列化与反序列化的单例模式实现
private static final long serialVersionUID=4448L;
//内部类方法
private static class Object6_10Handler{
private static final Object6_10 obj=new Object6_10();
}
public Object6_10() {
}
//第一次实例化时要调用该方法
public static Object6_10 getInstance(){
System.out.println("getInstance"+Object6_10Handler.obj.hashCode());
return Object6_10Handler.obj;
}
//readResolve反序列化时自动调用
protected Object readResolve() throws ObjectStreamException{
System.out.println("调用了readResolve()方法");
System.out.println("readResolve"+Object6_10Handler.obj.hashCode());
return Object6_10Handler.obj;
}
}
测试类:
public class Test6_10 {
//序列化与反序列化的单例模式实现
public static void main(String[] args) {
try{
Object6_10 obj=new Object6_10().getInstance(); //实例化时显式调用getInstance
FileOutputStream fosRef=new FileOutputStream(new File("666.txt")); //输出一个文件
ObjectOutputStream oosRef=new ObjectOutputStream(fosRef);
oosRef.writeObject(obj); //向文件输出一个Object6_10类对象
oosRef.close();
fosRef.close();
System.out.println(obj.hashCode());
FileInputStream fisRef=new FileInputStream(new File("666.txt")); //读取该文件
ObjectInputStream oisRef=new ObjectInputStream(fisRef);
Object6_10 obj1=(Object6_10) oisRef.readObject(); //从文件中读取Object6_10类对象
oisRef.close();
fisRef.close();
System.out.println(obj1.hashCode());
}catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
getInstance366712642
366712642
调用了readResolve()方法
readResolve366712642
366712642
更多关于序列化,反射等和单例模式的知识可以访问博客:
https://www.cnblogs.com/ttylinux/p/6498822.html?utm_source=itdadao&utm_medium=referral
4.3 使用static代码块实现
public class Object6_11 {
//使用static代码实现单例模式
private static Object6_11 ins=null;
public Object6_11() {
}
//static代码块
static{
ins=new Object6_11();
}
public static Object6_11 get(){
return ins;
}
}
在线程中使用Object6_11.get().hashCode()
输出,在多线程环境下可以实现单例模式。但在序列化与反序列化时仍要加readResolve()方法。
5.使用枚举类实现单例模式
- 介绍:
最被推荐使用的一种方式,在使用枚举类时构造方法会自动调用,自带了readResolve的解决方式,但注意使用时不能违反“职责单一原则”,不能将enum类暴露在外。 - 一个例子,使用枚举实现单例模式–JDBC连接
测试类:public class Object6_13 { //完善使用enum枚举数据类型实现单例模式 //内部枚举类 public enum mySingleton{ connectionFactory; private Connection conn; private mySingleton(){ try{ System.out.println("调用了Object6_12的构造"); String url="jdbc:mysql://localhost:3306/employee?characterEncoding=utf8&useSSL=true"; String username="root"; String PASSWORD = "******"; String diver="com.mysql.jdbc.Driver"; Class.forName(diver); conn=DriverManager.getConnection(url,username,PASSWORD); }catch (Exception e){ e.printStackTrace(); } } public Connection get(){ return conn; } } public static Connection getcon(){ return mySingleton.connectionFactory.get(); } }
public class Test6_13 { //完善使用enum枚举数据类型实现单例模式 public static void main(String[] args) { Thread6_13 t1=new Thread6_13(); Thread6_13 t2=new Thread6_13(); Thread6_13 t3=new Thread6_13(); t1.start(); t2.start(); t3.start(); } }
最后更新: 2018年05月18日 21:19
原始链接: https://zjxkenshine.github.io/2018/03/05/《Java多线程编程核心技术》笔记(六)/