相关链接:
【Java多线程编程核心技术】1.Java多线程技能-笔记总结
【Java多线程编程核心技术】2.对象及变量的并发访问(上)-笔记总结
【Java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结
【Java多线程编程核心技术】3.线程间通信 -笔记总结
【Java多线程编程核心技术】4.Lock的使用-笔记总结
【Java多线程编程核心技术】5.定时器Timer-笔记总结
【Java多线程编程核心技术】6.单例模式与多线程-笔记总结
【Java多线程编程核心技术】7.拾遗增补-笔记总结
立即加载/“饿汉模式”
常见实现办法就是直接 new 实例化,也就是调用方法前,实例已经被创建了。
public class MyObject {
// 立即加载方式==饿汉模式
private static MyObject myObject = new MyObject();
private MyObject() {
}
public static MyObject getInstance() {
// 此代码版本为立即加载
// 此版本代码的缺点是不能有其它实例变量
return myObject;
}
}
立即加载型单例设计模式
延迟加载/“懒汉模式”
延迟加载/“懒汉模式”解析
延迟加载就是在调用get()方法时实例才被创建,常见实现方法是在get()方法中进行实例化。
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
// 延迟加载
if (myObject != null) {
} else {
myObject = new MyObject();
}
return myObject;
}
}
延迟加载/“懒汉模式”的缺点与解决方案
在多线程的的环境中,会出现取出多个实例的情况,与单例模式的初衷是相背离的。
package extthread;
import test.MyObject;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
package test;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
package test.run;
import extthread.MyThread;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
输出结果:
297229282
485579652
1560766941
创建出3个对象,并不是单例的,也就是“错误的单例模式”
解决方案:
1.声明synchronized关键字:在多线程同时进入的getInstance()方法处声明synchronized关键字(或该方法里全局的synchronized同步代码块),但这种方法运行效率非常低下,是同步运行。
2.针对某些重要的代码进行单独的同步:
package test;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
// 使用synchronized (MyObject.class)
// 虽然部分代码被上锁
// 但还是有非线程安全问题
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
从语句的结构上来讲,运行的效率的确得到了提升,但遇到多线程的情况下还是五法解决得到同一个实例对象的结果。
使用DCL双检查锁机制:
使用 双检查锁机制来实现多线程环境中的延迟加载单例设计模式
package test;
public class MyObject {
private volatile static MyObject myObject;
private MyObject() {
}
// 使用双检测机制来解决问题
// 即保证了不需要同步代码的异步
// 又保证了单例的效果
public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
synchronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
// 此版本的代码称为:
// 双重检查Double-Check Locking
}
使用双重检查锁功能,成功地解决了“懒汉模式”遇到多线程的问题。
使用静态内置类实现单例模式
package test;
public class MyObject {
// 内部类方式
private static class MyObjectHandler {
private static MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
}
序列化与反序列化的单例模式实现
package test;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class MyObject implements Serializable {
private static final long serialVersionUID = 888L;
// 内部类方式
private static class MyObjectHandler {
private static final MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法!");
return MyObjectHandler.myObject;
}
}
package test.run;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import test.MyObject;
public class SaveAndRead {
public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File(
"myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
FileInputStream fisRef = new FileInputStream(new File(
"myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
MyObject myObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果:
356406356
调用了readResolve方法!
356406356
使用static代码块实现单例模式
package test;
public class MyObject {
private static MyObject instance = null;
private MyObject() {
}
static {
instance = new MyObject();
}
public static MyObject getInstance() {
return instance;
}
}
应用静态代码块的这个特性来实现单例设计模式
完善使用enum枚举数据类型实现单例模式
枚举enum和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,也可以应用这个特性实现单例设计模式
package test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class MyObject {
public enum MyEnumSingleton {
connectionFactory;
private Connection connection;
private MyEnumSingleton() {
try {
System.out.println("创建MyObject对象");
String url = "jdbc:sqlserver://localhost:1079;databaseName=y2";
String username = "sa";
String password = "";
String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
Class.forName(driverName);
connection = DriverManager.getConnection(url, username,
password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection() {
return connection;
}
}
public static Connection getConnection() {
return MyEnumSingleton.connectionFactory.getConnection();
}
}