1、立即加载/饿汉模式
// 立即加载/饿汉模式
public class MyObject {
private static final MyObject myObject = new MyObject();
private MyObject() {
}
public static MyObject getInstance() {
return myObject;
}
}
立即加载/饿汉模式是在类创建的同时已经创建好一个静态的对象供系统使用,不存在线程安全问题
2、延迟加载/懒汉模式
// 延迟加载/懒汉模式
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
if (myObject == null) {
myObject = new MyObject();
}
return myObject;
}
}
延迟加载/懒汉模式是在调用方法时实例才被创建,在多线程环境下,会出现取出多个实例的情况,与单例模式的初衷是相背离的
1)、延迟加载/懒汉模式在多线程环境下创建出多个实例:
// 延迟加载/懒汉模式
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject == null) {
TimeUnit.SECONDS.sleep(3);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
public class Run {
public static void main(String[] args) {
MyThread myThread = new MyThread();
MyThread myThread2 = new MyThread();
MyThread myThread3 = new MyThread();
myThread.start();
myThread2.start();
myThread3.start();
}
}
运行结果:三次打印的hashCode不完全相等
2)、通过声明synchronized关键字解决线程安全问题:
// 延迟加载/懒汉模式
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static synchronized MyObject getInstance() {
try {
if (myObject == null) {
TimeUnit.SECONDS.sleep(3);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
使用synchronized关键字,这种方法的运行效率很低,是同步运行的,下一个线程想要取得对象,则必须等上一个线程释放锁之后,才可以继续执行
3)、使用同步代码块解决线程安全问题:
// 延迟加载/懒汉模式
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
// 相当于public static synchronized MyObject getInstance()
synchronized (MyObject.class) {
if (myObject == null) {
TimeUnit.SECONDS.sleep(3);
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
加入同步代码块,这种方法的运行效率也是非常低,和synchronized同步方法一样是同步运行的
4)、针对某些重要的代码进行单独的同步
// 延迟加载/懒汉模式
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject == null) {
TimeUnit.SECONDS.sleep(3);
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
此方法只对实例化对象的关键代码进行同步,从语句的结构上来讲,运行的效率的确得到了提升。但如果是多线程的情况下还是无法解决得到同一个实例对象的结果
5)、使用DCL双检查锁机制
// 延迟加载/懒汉模式
public class MyObject {
private volatile static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject == null) {
TimeUnit.SECONDS.sleep(3);
synchronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
使用DCL双检查锁机制,既保证了不需要同步代码的异步执行性,又保证了单例的效果
3、使用静态内部类实现单例模式
public class MyObject {
private static class MyObjectHandler {
private static MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
}
4、使用静态代码块实现单例模式
public class MyObject {
private static MyObject instance = null;
private MyObject() {
}
static {
instance = new MyObject();
}
public static MyObject getInstance() {
return instance;
}
}
5、使用enum枚举实现单例模式
public enum Singleton {
INSTANCE;
private Singleton() {
}
}
为什么最好的单例模式是枚举单例:
1)、枚举类能防止破坏单例模式
- 枚举类能防止利用反射方式获取枚举对象
原因:调用反射newInstance方法时会检查是否为枚举类,如果是将报错,错误如下:
Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects
- 枚举类能防止使用序列化与反序列化获取新的枚举对象
原因:在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法
2)、线程安全