一、介绍二、单例模式的种类
2.1、饿汉式:
2.1.1、代码实现2.1.2、注意事项 2.2、懒汉式
2.2.1、代码实现 2.3、双重检测模式
2.3.1、代码实现 2.4、静态代码块模式
2.4.1、代码实现 2.5、枚举模式
2.5.1、代码实现
一、介绍单例模式是指在内存中只会创建且仅创建一次对象的设计模式。应用场景在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。 二、单例模式的种类 2.1、饿汉式:
饿汉式实现的手法有两种:(1)静态常量,(2)静态代码块;在类加载时已经创建好该单例对象,等待被程序使用 2.1.1、代码实现
静态常量
public class Hungry { public static final Hungry hungry = new Hungry(); //必须要将构造器用private进行修饰 private Hungry() { } //通过该方法获取hungry private static Hungry getInstance() { return hungry; } }
静态代码块
public class Hungry { public static final Hungry hungry2; //在静态代码块里初始化 static { hungry2 = new Hungry(); } //必须要将构造器用private进行修饰 private Hungry() { } //通过该方法获取hungry private static Hungry getInstance2() { return hungry2; } }2.1.2、注意事项
这种方式只适合在单线程中使用;这种方式可能存在内存浪费; 2.2、懒汉式
在真正需要使用对象时才去创建该单例类对象;懒汉式一共有三种方式,每种方式各有弊端。 2.2.1、代码实现
方式一:通过判断single是否被初始化,从而进行对象的实例化。如果该对象还未被实例化,则会进行实例化。如果已经实例化,则不会再实例化。但是,这种方式存在线程安全问题。假设有一个线程刚刚将if (null == single)条件实行完,时间片就完了。然后另一个线程又来获取该对象,此时,single还未被初始化,所以第二次进来的线程会进行new过程。当第一次的那个线程再次获取到时间片后,会从上一次运行的地方接着运行,所以会再次执行new *** 作。所以,一个实例被new了两次,这就不符合单例模式的定义了。优缺点说明:该模式起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if(single == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以,在多线程环境下不可使用这种方式。结论:在实际开发中,不要使用这种方式。
public class Single { private static Single single; private Single() { } //懒汉模式一:存在线程安全问题 private static Single getInstance() { if (null == single) { single = new Single(); } return single; } }
方式二:通过给方法加上锁,从而避免了多个线程同时进入该方法中去。因此不会造成单线程安全问题。但是,单例模式只需要new一次,所以,如果这样写,在每次调用该方法时都要进行锁的获取,如果该方法被其他线程占用着,则下一个线程到来后不能及时运行该方法。从而降低效率。优缺点说明:解决了线程不安全问题。效率太低了,每个线程在想获得实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就行了。后面想获得该实例,直接return就行。方法同步效率太低了。结论:在实际开发中,不推荐使用这种方式。
public class Single { private static Single single; private Single() { } //懒汉模式二:不存在线程安全模问题。 //但是每次get都要进行同步,效率太低。毕竟单例只要new一次。 private static synchronized Single getInstance2() { if (null == single) { single = new Single(); } return single; } }
方式三:因为方式二解决了多线程安全问题,所以就有人提出,可以通过改变锁的位置,从而解决方式二的效率低下的问题。更改方式如下面代码所示;但是,通过更改代码,虽然解决了效率低下的问题,但是又将线程不安全问题给再次引入。理解方式,和方式二同理。优缺点说明:这种方式本意是对上一个方式的改进,因为上一个方式的效率太低。但是这种同步并不能起到线程同步的作用。结论:在实际开发中,不能使用这种方式。
public class Single { private static Single single; private Single() { } //懒汉模式三:解决了效率问题,但是有存在线程安全问题 private static Single getInstance3() { if (null == single) { synchronized (Single.class) { single = new Single(); } } return single; } }2.3、双重检测模式
这种模式是将上面懒汉模式进行的改良后的产物。经过·上面的分析,相信大家对懒汉模式的优缺点都已经很清楚了。但是上面的懒汉模式都不适合在实际开发中去使用。为了在实际开发中更好的使用,因此对上面的懒汉模式进行再次的改良。 2.3.1、代码实现
通过上面分析可以得出,加锁可以解决线程安全问题,但是如何合理的将锁应用到这个里面,从而使得该方法即可以解决多线程安全问题,还可以解决效率过低的问题。解决方案:首先通过if条件判断,从而得知成员doubleCheck是否被实例化。如果一个线程刚刚完成第一个条件判断时间片就用完了,当第二个线程完成实例化后,第一个线程再次获取到锁后,因为doubleCheck被volatile修饰,所以在第二个线程完成实例化后,当第一个线程再次获取到时间片后,就会检测到该成员已经实例化就不会再次实例,从而解决了多线程问题,同时当有其他线程再次运行该方法时,由于该成员已经被实例化了,从而就不会再次实例化,会直接返回已经实例化好的成员。从而也解决了效率太低的问题。
public class DoubleCheck { //注意,要加上volatile关键字 private volatile static DoubleCheck doubleCheck; private DoubleCheck() { } private static DoubleCheck getInstance() { if (null == doubleCheck) { synchronized (DoubleCheck.class) { if (null == doubleCheck) { doubleCheck = new DoubleCheck(); } } } return doubleCheck; } }2.4、静态代码块模式
这种方式采用了类加载的机制来保证初始化实例时只有一个线程。静态内部类方式在StaticClass类被加载时并不会立即实例化,而是在需要实例化时,调用getInterface方法,才会装载SingleInstancce类,从而完成StaticClass实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性。在类初始化时,别的线程是无法进入的。优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。结论:推荐使用。 2.4.1、代码实现
public class StaticClass { private StaticClass() { } private static class StaticInstance { private static final StaticClass INSTANCE = new StaticClass(); } private static StaticClass getInstance() { return StaticInstance.INSTANCE; } }2.5、枚举模式
这借助JDK1.5添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重现创建创建新的对象。这种方式是Effectiive Java作者Josh Bloch提倡的方式。结论:推荐使用 2.5.1、代码实现
public enum EnumWay { INSTANCE; public void method() { System.out.println("OK"); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)