面试的时候可能会问到单例设计模式,Java的单例设计模式有,饿汉(线程安全)和懒汉(线程不安全)。
单例设计模式顾名思义是该模式只有单个实例对象,单例设计模式需要将自己的构造方法私有化(外部无法通过构造方法创建对象),自己内部创建对象,提供外部获取实例的方法(该方法必须为public static,因为外界无法创建对象调用方法只能通过调用静态方法获取对象实例)。
饿汉:字面意思是太饿了,获取实例前他自己就先创建了实例,不等人,自己做。
class Single{
private static Single s = new Single();//JVM加载字节码文件后,自动创建了一份对象,来获取实例时直接返回这一份实例
private Single(){}
public static Single getInstance(){
return s;
}
懒汉:字面意思是太懒了,等到获取实例的时候才去创建,等你来我再做。
class Single{
private static Single s = null;//JVM加载字节码文件后,对象引用为null,等调用getInstance()方法才去创建对象
private Single(){}
public static Single getInstance(){
if (s == null){//如果对象为null,就去创建对象
s = new Single();
}
return s;
}
}
上面的代码看起来没什么问题,饿汉天生是线程安全的就不考虑了,懒汉在多线程下是有问题的。
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Single.getInstance());
}
};
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
我们开启了3条线程运行结果显示:
test.Single@581942c
test.Single@5352bf9f
test.Single@7c3a3e59
出现了打印结果出现不一样,当然也有全部一样的情况,也有一条不一样的情况,不是说单例吗?为什么出现不一样的引用?
因为有可能三条线程同时执行到了下面
public static Single getInstance(){
if (s == null){---------------------------同时执行到这,此时都为null,然后都执行下面的创建对象,出现对象引用被覆盖
s = new Single();
}
return s;
}
学习了多线程后可以加 synchronized 关键字在方法上实现加锁同步
public static synchronized Single getInstance(){
if (s == null){
s = new Single();
}
return s;
}
这样虽然实现了线程安全,但是有弊端,因为synchronized
public static synchronized Single getInstance(){
Thread.sleep(10000); ---假如等待10秒,将会出现所有线程都要执行10秒才轮到下一个线程执行,3线程就执行30秒,多线程为了提高效率但却适得其反了这样。
if (s == null){
s = new Single();
}
return s;
}
改进:
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if (s == null){//这个判断有助于第一次获取实例以后的效率
synchronized (Single.class){//锁起来
if (s==null){//锁起来后再判断一次是不是为null,如果不判断则会出现在创建的安全问题
s = new Single();
}
}
}
return s;
}
}
以上的懒汉单例设计模式就是线程安全的了,并且其效率较之直接锁整个方法也大大提高了。