单例模式是java中大家都应该听说过的,比较学安卓的先学java,当然设计模式并不是只有java语言才有这么一说法,其他都有设计模式这一说法。java有23种设计模式,今天就说说单例模式。
如果你一个类需要消耗很多的资源或者你只需要一个实例存在的时候,就需要用到单例了,尤其是多线程情况下。比如你登录app后需要保存用户名和密码等,而你整个app所有界面都是用的这个账号信息,所以此时你可以使用单例存下,整个app使用过程只使用一个实例,节约内存。同理,整个app用来用去都是那个数据库,所以你数据库的管理类也可以用单例,等等等等。。。总而言之,为了确保唯一性,就使用单例吧。
下面介绍6种单例的实现方式。介绍前先说下,所有单例类的构造方法需要我们重写出来并且私有化,如果不写构造方法,java默认有个无参的构造方法并且是public,这样导致该类可以被new出来,失去了单例效果,所以我们必须自己写出构造方法并且私有化,不能不写,该类的实例化对象也必须是静态的,这样才能通过类名进行调用,而不是new了。
【第一种,饿汉单例模式】:
//饿汉单例模式
public class ClassOne {
private static final ClassOne classOne = new ClassOne();
private ClassOne(){
}
public static ClassOne getInstance(){
return classOne;
}
}
使用方式,ClassOne.getInstance.xxxxxxx;
可以看到,饿汉单例模式,声明的时候已经初始化了该类的一个实例,并且构造方法是private,无法被new出来,保证了唯一性,当我们需要的时候直接getInstance()返回我们声明的时候就已经初始化的该类的对象。
【第二种,懒汉单例模式】:
//懒汉单例模式
public class ClassTwo {
private static ClassTwo classTwo;
private ClassTwo(){
}
public static synchronized ClassTwo getInstance(){
if(classTwo == null){
classTwo = new ClassTwo();
}
return classTwo;
}
}
使用方式:ClassTwo.getInstance.xxxxxxx;
懒汉单例模式可以看出,区别就是声明的时候没有进行初始化,而是第一次调用的时候才初始化。
饿汉和懒汉的区别:
1.饿汉是声明的时候就初始化;懒汉是第一次调用才初始化,所以懒汉在一定程度上节约了资源。两者的区别形象点就是一个人在家做了面包,饿汉就是一个超级饿的人,边做边吃,因为他饿!!!吃货!!!而懒汉就是懒得去吃,饿了再去吃!!!
2.懒汉多了个synchronized 同步锁,保证多线程情况下的唯一性。
而饿汉之所以不需要加,是因为声明的时候就已经初始化了,所以当调用getInstance是不会创建对象的,即使多个线程同时调用也不会创建,保证了唯一性,而懒汉不一样,懒汉是调用getInstance的时候才会初始化,可能导致多个初始化同时进行。
【第三种,DCL单例模式】,其实DCL单例模式可以看成是从懒汉改进的:
//DCL单例模式(DCL,double check lock,先检查,再锁,锁之后再检查一次),即双重检查锁定
public class ClassThree {
private static ClassThree classThree;
private ClassThree(){
}
public static ClassThree getInstance(){
if(classThree == null){ //检查
synchronized (ClassThree.class) { //锁
if(classThree == null){ //检查
classThree = new ClassThree(); //实例化
}
}
}
return classThree;
}
}
使用方式:ClassThree.getInstance.xxxxxxx;
DCL一词我已经在代码中解释了,不多说。上面代码中可以看到跟懒汉的区别在于synchronized同步锁写在了getInstance()方法里面 并且多一次空判断。为何这样改进呢?因为懒汉中每次调用getInstance()都会进行同步,浪费资源,所以先进行空判断,如果已经实例化了,就直接用,不需要进行同步和初始化,节约资源;如果第一次调用,那就进行同步,并且初始化。
【第四种,静态内部类单例模式】:
//内部类单例模式
public class ClassFour {
private ClassFour(){
}
public static ClassFour getInstance(){
return ClassFourHolder.classFour;
}
private static class ClassFourHolder{
private static ClassFour classFour = new ClassFour();
}
}
使用方式:ClassFour.getInstance.xxxxxxx;
以我个人理解,可以将这个看成是饿汉改进而来的,饿汉是类的声明中就初始化,而上面这个内部类方式也是在内部类的声明中就初始化,所以首先,我们不需要synchronized关键字了,原因在上面说过了,而相对于饿汉,这样的改进好处就是改类第一次加载时并不会像饿汉单例模式一样声明了,因为它的声明写在了内部类中,只有当调用getInstance里面使用内部类的时候才会初始化,所以这种方式比饿汉节约了资源。所以推荐使用第四种内部类模式,第三种解决了资源浪费,同步多余等问题,但是在某些特殊情况下会失效,汗颜,具体原因我也解释不清楚,因为自己对这种方式也了解得不是很透彻,见谅,呜呜。
【第五种,枚举单例模式】,简直太简洁了:
public enum ClassFive {
INSTANCE;
}
没错,你没有看错,不要一千行,也不要100行,只需要几行!
使用方式:ClassFive.INSTANCE.xxxxx;
对于前面几种方法都有一个问题,那就是序列化操作,java的序列化和反序列化我就不哔哩哔哩了,当我们序列化后再反序列化读取回来的时候,会重新生成对象!而枚举不会,不过枚举的使用有限制,所以我一般用第四种:静态内部类单例模式
【第六种,容器单例模式】,这种太另类了,基本没人用:
//容器单例模式
public class ClassSix {
//创建容器
private static Map<String,Object> maps = new HashMap<>();
//构造函数
private ClassSix(){
}
//将单例类放入容器
public static void setSingletonClass(String key,Object value){
if(!maps.containsKey(key)){
maps.put(key, value);
}
}
//根据key取出单例类
public static Object getSingletonClass(String key){
return maps.get(key);
}
}
这个没啥解释的了,就是Map的用法,注释已经很清楚了,存取类的实例而已。
JAVA中的单例模式用法就我知道的只有这六种了,个人推荐第三种和第四种,而且这两种应该是使用比较多的两种单例实现方式了,第三种只要线程并发别太严重,一般情况下完全可以使用的。
单例的实现方式和使用已经说完了,如果有什么不对的欢迎指教~