1.懒汉式:
package main;
public class Singleton1 {
private static Singleton1 instance = null;
private static int count = 0;
private Singleton1(){
count ++;
}
public static Singleton1 getInstance(){
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
public static int getCount() {
return count;
}
}
最开始instance = null,开始不会创建任何实例,直到需要实例时,才会创建惟一一个。节约空间。
但是有一个问题,在多线程环境下,可能有2个线程进入到instance == null内部,那么就会创建2个实例,具体返回哪个也不确定,可能返回各自的,也可能返回最后创建的。
例子:
package main;
public class Main {
public static void main(String[] args) {
for(int i=0;i<10;i++){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Singleton1.getInstance();
}
});
thread.start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Singleton1.getCount());
}
}
最后打印2,说明创建了2个实例。
2.方法级别互斥:
package main;
public class Singleton2 {
private static Singleton2 instance = null;
private static int count = 0;
private Singleton2(){
count ++;
}
public synchronized static Singleton2 getInstance(){
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
public static int getCount() {
return count;
}
}
可以解决多线程,但是锁的代码范围太大,所有的getInstanse操作都会互斥,即使已经不为null了,只是简单的返回。因此效率不高。其实互斥操作应该是针对第一次获取实例时的创建操作,以后的就是直接return了,不需要互斥。
3.块锁:
public static Singleton3 getInstance(){
synchronized (Singleton3.class) {
if (instance == null) {
instance = new Singleton3();
}
}
return instance;
}
这样表面上解决了问题,其实效率方面和2一样。因为每一次的if操作还是被互斥了,事实上,一旦instance被创建,之后的操作只执行return,压根不需要进入同步块内。我们最终想要的是不让if操作互斥,但是一旦if == null了,最后的new操作是互斥的。这是最理想的结果。
那么想到改成这样:
public static Singleton3 getInstance(){
if (instance == null) {
synchronized (Singleton3.class) {
instance = new Singleton3();
}
}
return instance;
}
表面上也好像可以,但实际上这样写根本不会互斥了。和不用synchronized没区别了。
4.双重检验:
通过上面的讨论可以知道,if == null的操作不能放在块锁内,而new操作在块内。这样的话只能在块锁内再增加一次==null的判断:
public static Singleton3 getInstance(){
if (instance == null) {
synchronized (Singleton3.class) {
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
这就是延迟加载的线程安全的单例模式。
5.饿汉式:
在类加载的时候就创建实例,不同担心多线程问题。但是浪费了一定的空间。
package main;
public class Singleton4 {
private static Singleton4 instance = new Singleton4();
private static int count = 0;
private Singleton4(){
count ++;
}
public static Singleton4 getInstance(){
return instance;
}
public static int getCount() {
return count;
}
}
6.静态内部类
public class Singlton5 {
private Singlton5(){
}
private static class Holder{
public static Singlton5 singlton5 = new Singlton5();
}
public Singlton5 getInstance(){
return Holder.singlton5;
}
}
这个同时具备了延迟加载以及线程安全的特性,而且书写很简单,是项目中比较推荐的模式。
线程安全?静态实例变量的赋值语句是在类加载的最后一步执行的,虚拟机将所有static变量的初始化语句弄成一个方法,执行,并且虚拟机可以保证这个类是只执行一次的。https://blog.csdn.net/u010900754/article/details/77873948
延迟?不调用getInstance方法就不会加载Holder类,也就不会new实例。
这种方式在一般情况下已经足够了,但是仍然无法防止反射和序列化的破坏。
7.枚举
public enum Singlton6 {
Instance;
private Singlton6(){
}
}
枚举单例可以防止反射和序列化破坏,这里看下反编译代码:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Singlton6.java
package com.liyao.t;
public final class Singlton6 extends Enum
{
public static Singlton6[] values()
{
return (Singlton6[])$VALUES.clone();
}
public static Singlton6 valueOf(String s)
{
return (Singlton6)Enum.valueOf(com/liyao/t/Singlton6, s);
}
private Singlton6(String s, int i)
{
super(s, i);
}
public static final Singlton6 Instance;
private static final Singlton6 $VALUES[];
static
{
Instance = new Singlton6("Instance", 0);
$VALUES = (new Singlton6[] {
Instance
});
}
}
这其实是一种懒汉式的方式,提前实例化。
这里只看下如何防止反射?
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
反射的newInstance方法会阻止构建枚举的实例。
补充
双重锁定,new一个对象时,由于new操作是多个指令,可能有指令重排序,导致对象没有初始化但就对外返回引用。所以需要加一个volatile关键字。