如果一个类是单例类,那么这个类只能实例化一个实例,并且单例类必须能够自己创建自己的唯一实例(所以单例类的构造器必须是私有的);
使用背景:
一个全局使用的类频繁地创建与销毁,比如之前我曾负责人员电子档案开发,这里涉及到一个红名单的概念,比如我们要查看某个人的档案,但是每次查看档案前都需要判断这个人是否为红名单中的人物,如果是的话那么就无权限查看该人员的档案。这里的红名单其实就一系列存储在数据库中的身份证,而我们每次查看某个人的档案时都需要进行一次数据库交互,判断是否为红名单中的人物,我们知道频繁与数据库交互并不是什么好事,而单例模式在这里可以很好的解决这个问题,我们再系统启动的时候可以将红名单中的数据加载到缓存中,之后每次都来缓存取这个红名单并判断当前被查人员是否为红名单人员即可。
单例模式分为立即加载(饿汉模式)和延迟加载(懒汉模式)
立即加载
立即加载是指在调用方法调用前,实例就已经被创建缓存在内存中,且看如下代码:
public class MyObject {
private static MyObject myObject = new MyObject();
private MyObject() {
}
public static MyObject getInstance() {
return myObject;
}
}
这种创建方式存在一种缺陷是该myObject单例中不能存放其他的变量,如果有MyObject中存在其他变量,则需要在getInstance中进行初始化操作,但是因为getInstance()方法没有同步,所以有可能出现非线程安全的问题。
延迟加载
延迟加载是指在调用get()方法时实例才被创建,常见的实现办法就是在get()方法中进行new实例化,如:
public class MyObject {
private static MyObject myObject = new MyObject();
private MyObject() {
}
public static MyObject getInstance() {
//在多线程环境中,该代码无法实现保持单例
if (myObject == null) {
myObject = new MyObject();
}
return myObject;
}
}
使用延迟加载MyObject类在单线程情况确实实现了单例模式,但是在多线程并发下就有可能出现多个MyObject的实例,因为getInstance()不是同步方法,我们可以在getInstance()方法上加入同步synchronized关键字来解决多线程下单例问题,但是这种方法有时候效率会非常低下,因为是同步运行的,下一个线程想要取得对象,则必须等上一个线程释放锁之后才可以继续执行。我们再来看看下面这段代码:
public class MyObject {
private volatile static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
if (myObject == null) {
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
return myObject;
}
}
如上代码,我们使用同步代码块或使用lock来保证关键的代码部分同步,而其他代码不需要同步,但是这样还是会存在非线程安全,因为有可能会存在多个线程在lock(obj)这个地方进行排队实例化,这样依然会实例化出多个不同的实例对象。
解决多线程单例的方法:
双检锁/双重校验锁(DCL,即 double-checked locking)
public class MyObject {
private volatile static MyObject myObject;
private MyObject() {
}
// 使用双检测机制来解决问题,即保证了不需要同步代码的异步,又保证了单例的效果
public static MyObject getInstance() {
if (myObject == null) {
synchronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
return myObject;
}
}
使用内置静态类实现
public class MyObject implements Serializable {
private static final long serialVersionUID = 888L;
private MyObject() { }
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
//如果注释掉该接口方法,则无法实现序列化和反序列化在单例模式中的实现
protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法!");
return MyObjectHandler.myObject;
}
// 内部类
private static class MyObjectHandler {
private static final MyObject myObject = new 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) {
e.printStackTrace();
} catch (IOException e) {
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();
}
运行结果:
29310343
调用了readResolve方法!
29310343
使用static代码块实现
静态代码块中的代码在使用的时候寄已经执行了,所以可以该特性来实现单例设计模式。
public class MyObject {
private static MyObject instance = null;
private MyObject() { }
static {
instance = new MyObject();
}
public static MyObject getInstance() {
return instance;
}
}
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(MyObject.getInstance().hashCode());
}
}
}
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();
}
}
运行结果:
17388941
17388941
17388941
17388941
17388941
17388941
17388941
17388941
17388941
使用enum枚举数据类型实现
枚举enum和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,也可以使用该特性实现单例设计单例模式。
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();
}
}
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(MyObject.getConnection().hashCode());
}
}
}
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();
}
}
运行结果:
创建MyObject对象
10440721
10440721
10440721
10440721
10440721
10440721
10440721
10440721
10440721
单例模式与多线程相关参考资料:
http://www.runoob.com/design-pattern/singleton-pattern.html
http://blog.jobbole.com/109449/