单例模式时指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,在我们生活中有很多例子,比如一个公司只有一个CEO。
下面来看下单例模式的几种实现:
饿汉式单例:
饿汉式单例模式在类加载时就创建好对象,等着调用者来获取这个对象
优点:绝对的线程安全,因为没有加锁,执行效率高
缺点:不管这个对象有没有被用到都会占着内存,浪费资源
public class SingletonHungry {
public static SingletonHungry SINGLETONHungry = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return SINGLETONHungry;
}
}
懒汉式单例:
优点:实现了懒加载,适合在单线程下使用
缺点:多线程下会存在线程不安全的问题
public class Singleton1 {
public static Singleton1 singleton1 = null;
private Singleton1() {
}
public static Singleton1 getInstance() {
if (singleton1 == null) {
singleton1 = new Singleton1();
}
return singleton1;
}
}
根据上面这个懒汉式单例我们可以做优化:
public class Singleton2 {
public static Singleton2 singleton1 = null;
private Singleton2() {
}
public static synchronized Singleton2 getInstance() {
if (singleton1 == null) {
singleton1 = new Singleton2();
}
return singleton1;
}
}
上面Singleton2中给创建对象方法加入了同步锁,这样可以保证线程安全,但是效率低,我们还可以这样:
public class Singleton3 {
public static Singleton3 singleton3 = null;
private Singleton3() {
}
public static Singleton3 getInstance() {
if (singleton3 == null) {
synchronized (Singleton3.class) {
singleton3 = new Singleton3();
}
}
return singleton3;
}
}
在代码块中加入同步锁,这样会比在方法上加同步锁性能会高一些,同时也保证了对象实例只能创建一次,我们来模仿多线程情况测试一下:
public class Singleton3Test {
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(
new Runnable() {
public void run() {
Singleton3 singleton3 = Singleton3.getInstance();
System.out.println("Singleton3的hashCode:" + singleton3.hashCode());
}
});
Thread thread2 = new Thread(
new Runnable() {
public void run() {
Singleton3 singleton3 = Singleton3.getInstance();
System.out.println("Singleton3的hashCode:" + singleton3.hashCode());
}
});
thread1.start();
thread2.start();
}
}
测试方法运行结果:
Singleton3的hashCode:407939276
Singleton3的hashCode:1224672400
代码块中加同步锁结果还是没有保证线程的安全,下面我们对它再来改进一下:
public class Singleton4 {
public static Singleton4 singleton4 = null;
private Singleton4() {
}
public static Singleton4 getInstance() {
if (singleton4 == null) {
synchronized (Singleton4.class) {
if (singleton4 == null) {
singleton4 = new Singleton4();
}
}
}
return singleton4;
}
}
这次的运行结果:
Singleton4的hashCode:802393921
Singleton4的hashCode:802393921
可以看到这次保证了线程的安全,这种单例模式叫双重锁判断,其实还有一种方式可以实现懒汉式单例,那就是使用内部类加载机制,内部类只有在被调用的时候才会加载,利用JVM类加载时的线程安全特征来保证对象只加载一次
public class Singleton {
private Singleton() {
}
public static class a {
public static Singleton SINGLETON = new Singleton();
}
public static Singleton getInstance() {
return a.SINGLETON;
}
}
上面可以实现一个对象只能被创建一次,但是JAVA中还有很多方式可以创建新的对象,比如反射、反序列化、克隆下面我们需要防止这些操作再生成一个同样的对象
public class Singleton implements Serializable {
private static final long serialVersionUID = -7386993593450962036L;
public static Singleton singleton = null;
private Singleton() {
// 防止反射破坏单例
if (singleton != null) {
throw new RuntimeException("Singleton 只能被创建一次");
}
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
/**
* 防止反序列化破坏单例
*
* @return Singleton
*/
private Object readResolve() {
return singleton;
}
/**
* 防止克隆破坏单例
*
* @return
*/
@Override
public Singleton clone() {
return singleton;
}
}
这样就是一个比较完美的单例模式了,保证了线程安全,实现了懒加载,防止反序列化、克隆、反射来破坏单例。