一、"饿汉模式"
public class 饿汉模式 {
public static void main(String[] args) {
mySingleton theObject=mySingleton.getInstance();
mySingleton theObject2=mySingleton.getInstance();
System.out.println(theObject.hashCode()==theObject2.hashCode());//true
}
}
class mySingleton {
private static mySingleton theObject=new mySingleton();
private mySingleton(){}
public static mySingleton getInstance(){
return theObject;
}
}
使用static 在类加载时就完成对象实例的创建,之后返回同一对象 ,简单方便,但由于是立即加载的方式,难免会有些不必要的开销,比如此刻并不需要用到它
二、静态代码块("饿汉")
public class 静态代码块饿汉 {
public static void main(String[] args) {
mySingleton4 theObject=mySingleton4.getInstance();
mySingleton4 theObject2=mySingleton4.getInstance();
System.out.println(theObject.hashCode()==theObject2.hashCode());//true
}
}
class mySingleton4 {
private static mySingleton4 theObject=null;
private mySingleton4(){}
static{
theObject=new mySingleton4();
}
public static mySingleton4 getInstance(){
return theObject;
}
}
由于静态代码块static也只进行一次,且在对象创建前执行,所以返回的也都是同一单例
三、"懒汉模式"
public class 懒汉模式 {
public static void main(String[] args) {
mySingleton2 theObject=mySingleton2.getInstance();
mySingleton2 theObject2=mySingleton2.getInstance();
System.out.println(theObject.hashCode()==theObject2.hashCode());
}
}
class mySingleton2 {
private static volatile mySingleton2 theObject=null;
private mySingleton2(){}
public static mySingleton2 getInstance(){
if(theObject==null){
synchronized (mySingleton2.class) {
if(theObject==null)
theObject=new mySingleton2();
}
}
return theObject;
}
}
延迟加载,在第一次用到此实例时,创建唯一对象,由于可能存在多线程安全问题,故采用DCL 双重检测锁机制,确保只创建返回一个实例(但锁同步需要开销,注意!DCL 可能会发送happen-before指令重排错误,需要对实例附加volatile !不推荐使用DCL)
四、静态内部类单例
public class 静态内部类单例 {
public static void main(String[] args) {
mySingleton3 myObject=mySingleton3.getInstance();
mySingleton3 myObject2=mySingleton3.getInstance();
System.out.println(myObject.hashCode()==myObject2.hashCode());
}
}
class mySingleton3 {
private static class SingletonHandler{
private static mySingleton3 theObject=new mySingleton3();
}
private mySingleton3(){}
public static mySingleton3 getInstance(){
return SingletonHandler.theObject;
}
}
使用静态的内部类进行延迟加载,在第一次用到本实例时,加载返回内部类的static 对象,解决了饿汉模式的不必要开销问题和懒汉模式的线程同步开销问题
五、序列化单例
public class 单例与序列化 {
public static void main(String[] args) throws Exception{
mySingleton5 myObject = mySingleton5.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File(
"E://myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
FileInputStream fisRef = new FileInputStream(new File(
"E://myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
mySingleton5 myObject2 = (mySingleton5) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject2.hashCode());
}
}
class mySingleton5 implements Serializable{
private static class SingletonHandler{
private static mySingleton5 theObject=new mySingleton5();
}
private mySingleton5(){}
public static mySingleton5 getInstance(){
return SingletonHandler.theObject;
}
protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法!");
return SingletonHandler.theObject;
}
}
由于反射机制和序列化,会在返回时调用构造器生成一个新的对象,故违反了单例的原则。且序列化时是不会与静态方法有关联的,就导致了存入文件和读取文件的对象,不是同一实例对象。解决方式是 在单例中实现 readResolve方法,返回静态的对象。
在ObjectInputStream 类的源码中,存在一个 readOrdinaryObject(boolean unshared) 方法,其中判断了序列化对象是否有readResolve() 方法,若有则会去进行调用,则可在此方法内返回我们所需要的单例对象。
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//此处省略部分代码
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
//此处省略部分代码
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
关键在于 if 判断中desc.hasReadResolveMethod() ,Object rep = desc.invokeReadResolve(obj); 去执行readResolve 返回创新obj 。
关于单例与序列化可参考此引用博客:单例与序列化的那些事儿 深入分析Java的序列化与反序列化
六、枚举单例
public class enum枚举单例 {
public static void main(String[] args) throws Exception{
EnumSingleton hah=EnumSingleton.getInstance();
EnumSingleton hah2=EnumSingleton.getInstance();
mySingleton6 o1=hah.getSingleton6();
mySingleton6 o2=hah2.getSingleton6();
System.out.println(o1.hashCode()==o2.hashCode());
EnumSingleton myObject = EnumSingleton.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File(
"E://myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.getSingleton6().hashCode());
FileInputStream fisRef = new FileInputStream(new File(
"E://myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
EnumSingleton myObject2 = (EnumSingleton) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject2.getSingleton6().hashCode());
}
}
enum EnumSingleton{
SingletonHandler;
class mySingleton6{
private mySingleton6(){}
}
private mySingleton6 theObject=null;
private EnumSingleton(){
theObject=new mySingleton6();
}
public static EnumSingleton getInstance(){
return SingletonHandler;
}
public mySingleton6 getSingleton6(){
return theObject;
}
}
采用枚举可能是最合适的一种实现单例的方式,即可以避免线程安全问题,又可以完成序列化与反序列化的事。无需自己手动添加readResolve()方法去返回。
一般使用静态内部类的方式(若需要序列化则再加上readResolve()方法)和枚举的方式,当然最好的选择是枚举方式。