单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。但在多线程环境下,就可能会产生问题,并不一定能保证只有一个实例。具体问题描述和解决方法如下。
/*
* 多线程下的单例方式一:
* 饿汉式,提前加载,线程安全
*/
public class SingleDemo {
private static final SingleDemo s=new SingleDemo();
private SingleDemo(){}
public static SingleDemo getInstance(){
return s;
}
}
/*
* 多线程下的单例方式二:
* 懒汉式,延时加载,线程不安全
*/
public class SingleDemo {
private static SingleDemo s=null;
private SingleDemo(){}
public static SingleDemo getInstance(){
if(s==null)
s=new SingleDemo();
return s;
}
}
线程不安全分析:当线程1判断s==null时,发现为null,准备创建对象;此时,执行权切换到线程2,线程2判断s==null,发现也为null,也会创建新对象,这样在一个JVM会有多个单例类型的对象实例。解决线程不安全的方法如下:
public class SingleDemo {
private static SingleDemo s=null;
private SingleDemo(){}
//使用同步函数解决线程安全问题,使用的锁对象是SingleDemo.class
//问题:所有线程都需要判断锁,浪费资源
public static synchronized SingleDemo getInstance(){
if(s==null)
s=new SingleDemo();
return s;
}
}
public class SingleDemo {
private static SingleDemo s=null;
private SingleDemo(){}
//使用同步代码块解决线程安全问题,使用的锁对象是SingleDemo.class
//问题:所有线程都需要判断锁,浪费资源
public static SingleDemo getInstance(){
synchronized(SingleDemo.class){
if(s==null)
s=new SingleDemo();
}
return s;
}
}
使用同步函数或上述同步代码块解决线程安全问题带来的一个弊端就是,每个线程都需要判断同步锁,会浪费资源,降低效率,因此可以多加一次判断来解决资源浪费问题。
public class SingleDemo {
private static SingleDemo s=null;
private SingleDemo(){}
//解决线程同步效率问题
public static SingleDemo getInstance(){
if(s==null){//多加一步判断,就可以使得不是所有线程都判断锁,解决效率问题
synchronized(SingleDemo.class){
if(s==null)
s=new SingleDemo();
}
}
return s;
}
}
此外,还有另外一种不使用同步锁的解决办法,即使用私有的静态内部类,当不调用getInstance()方法时,就不会调用这个内部类,就不会产生实例。具体代码如下:
public class SingleDemo {
private SingleDemo(){}
//私有的静态内部类,当不调用getInstance()方法时,就不会调用这个内部类,就不会产生实例
private static class Inner{ //私有的静态内部类
static SingleDemo s = new SingleDemo();
}
public static SingleDemo getInstance(){
return Inner.s;
}
}