java是单进程语言吗_设计模式(Java语言)-单例模式

单例模式,简而言之就是在整个应用程序里面有且仅有一个实例,在程序的任何时候,任何地方获取到的该对象都是同一个对象。单例模式解决了一个全局的类被频繁创建和销毁的,或者每次创建或销毁都需要消耗大量cpu资源的对象的问题。单例模式总的可以分为懒汉模式和饿汉模式,顾名思义,懒汉模式是一个非常懒的汉子,只要你没有使用到它,它就永远不会实例化。饿汉模式的意思就是,汉子非常饥渴,只要在程序的编译阶段就给你分配内存,创建好对象。

将懒汉模式和饿汉模式细分,又可以分为:

1、懒汉模式

2、饿汉模式

3、双检模式

4、静态内部类模式

5、枚举模式

不管是用哪一种方式实现的单例模式,其创建流程基本都是一直的:首先将构造方法声明为private的,这样就防止直接new出一个新的对象。第二,声明一个私有的成员变量,即单例对象。第三步,声明一个public的静态方法,用于获取或创建单例对象,外部想要获取该对象必须通过这个方法获取。

一、懒汉模式1--线程安全

/**

* 饿汉模式1

*/

public class HungrySingleton1 {

private static HungrySingleton1 singleton = new HungrySingleton1();

private HungrySingleton1(){}

public static HungrySingleton1 getInstance() {

return singleton;

}

}

这种懒汉模式的优点是实现非常简单。缺点是并起到懒加载的效果,如果项目没有使用到这个对象的就会造成资源的浪费。

二、饿汉模式1--线程不安全

/**

* 懒汉模式1

*/

public class LazySingleton1 {

private static LazySingleton1 singleton;

private LazySingleton1(){}

public static LazySingleton1 getInstance() {

if (singleton == null) {

singleton = new LazySingleton1();

}

return singleton;

}

}

这种写法虽然实现了懒加载的效果,但是严格意义上并不是单例模式,因为在多线程的环境下有可能会创建出多个不同的对象,至于为什么,不懂的可以看一下我之间写的关于Java内存模型的文章。这种写法只能应用于单线程的环境下,局限性很大。实际中强烈不建议使用这种方法。

三、懒汉模式2--线程安全

/**

* 懒汉模式2

*/

public class LazySingleton2 {

private static LazySingleton2 singleton;

private LazySingleton2(){}

public static synchronized LazySingleton2 getInstance() {

if (singleton == null) {

singleton = new LazySingleton2();

}

return singleton;

}

}

这种写法咋看跟上面的方法一样,这种写法在方法上添加了 synchronized  关键字,这样就保证了每次只能有一个线程进入方法体中,解决了懒汉模式1中出现的问题。这种写法的优点是实现了懒加载的效果,缺点是效率非常低,当多个线程同时获取实例时,有可能会造成线程阻塞的情况。不推荐使用。

懒汉模式3--线程不安全

/**

* 懒汉模式3

*/

public class LazySingleton3 {

private static LazySingleton3 singleton;

private LazySingleton3(){}

public static LazySingleton3 getInstance() {

if (singleton == null) {

synchronized (LazySingleton3.class) {

if (singleton == null) {

singleton = new LazySingleton3();

}

}

}

return singleton;

}

}

这种写法进行了两次 singleton == null 的判断,在实际的应用中当我们调用这个方法时,其实99%的几率是实例就已经创建好了,因此第一个 singleton == null 能过滤掉99%的调用,不用将方法锁起来,从而提高了效率。这种方法的优点是实现懒加载的效果,效率和很高。缺点是代码设计仍然后缺陷,jvm在为对象分配内存和赋值并不是一个原子操作,即 singleton = new LazySingleton3() 这段代码在jvm中是由三个步骤实现的,首先jvm会在堆中为对象分配一定的内存空间,然后完成对象的初始化工作,然后将内存地址指向到对象中。但是,我们知道,jvm在编译的时候并不总是根据我们编写的代码的顺序来执行了,而是根据jvm觉得最优的顺序执行(这个过程就叫做指令重排序),所以有可能在执行了步骤1后就执行了步骤3,这时候第二个线程进来的发现singleton并不为空,因此就直接返回了该对象,因此造成空指针异常。

四、双重检查锁模式---线程安全

/**

* 懒汉模式4

*/

public class LazySingleton4 {

private volatile static LazySingleton4 singleton;

private LazySingleton4(){}

public static LazySingleton4 getInstance() {

if (singleton == null) {

synchronized (LazySingleton4.class) {

if (singleton == null) {

singleton = new LazySingleton4();

}

}

}

return singleton;

}

}

相较于上面的方式,这种方式只是在成员变量中添加了 volatile  关键字,解决了指令重排序的问题,同时确保当前线程修改了这个变量时,其他的线程能够及时读到最新的值。这种方法缺点是写起来比较复杂,要求程序员对jvm比较理解。优点是既保证了线程安全,同时也能够保证了比较高的效率。

五、静态内部类模式--线程安全

/**

* 懒汉模式5

*/

public class LazySingleton5 {

private LazySingleton5(){}

private static class Holder {

private static final LazySingleton5 INSTANCE = new LazySingleton5();

}

public static LazySingleton5 getInstance() {

return Holder.INSTANCE;

}

}

这种写法实现比较简单,即实现了懒加载的效果,同时也保证的多线程环境下的线程安全问题。推荐使用这种方式。

六、枚举模式 -- 线程安全

/**

* 懒汉模式6

*/

public enum LazySingleton6 {

INSTANCE

}

//使用方法

public class Test {

public static void main(String[] args) {

LazySingleton6 instance = LazySingleton6.INSTANCE;

LazySingleton6 instance1 = LazySingleton6.INSTANCE;

System.out.println(instance == instance1);

}

}

推荐写法,简单高效。充分利用枚举类的特性,只定义了一个实例,且枚举类是天然支持多线程的。

喜欢我写的博客的同学可以关注订阅号【Java解忧杂货铺】,里面不定期发布一些技术干活,也可以免费获取大量最新最流行的技术教学视频

04caa0c4abc54d00a9dfe24b2c666539.png

68f724bad5a33d9a9f6bd2ca7369ecb7.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值