标签:错误 变量 堆内存 嵌套 注意 操作 准备 情况 多线程
在程序员编写代码时候其实需要考虑到很多问题:可用性(完整的达到业务目的)
健壮性(程序在各种环境各种操作都能保证正常的运行)
复用性(代码能够被别的模块调用来达到不同的目的,这里一定程度上有解耦的思想)
可扩展性(代码设计足够灵活,能够适应尽可能多的业务)
性能
在根据以上的问题,对代码进行不断设计情况下,前辈们在一些常用通用的问题上面找到了其对应的最优解决方式(注意这是解决问题的方式而不是问题的答案,它更多表示一种思想,而我们的代码是这种 思想的载体)由此衍生出设计模式:设计模式之——单例模式。
01?单例定义
单例即为单个实例,在java中这表示一个类的对象有且只能有一个。
为什么需要单例大家跟我想一想,现实生活中,我们不可能为了满足一个人购物的需求去建一个商场。 而一个商场它可以为许多人执行卖东西的功能,那个人只需要跟其他人一样进这一个商场使用卖东西的 功能就行了。
所以发现没我们所有人达到同一个一个目的,很多时候我们需要的是它能做什么,而不是它本身。 我不会为了买东西去创建一个商场,我会跟其他人一样使用同一个商场的卖东西的功能。
在java中也一样,我们使用一个对象的方法,需要:
1、先创建这个对象
2、调用这个的对象方法
3、舍弃对象
一个人使用还好那么如果是千万的人呢?那是不是要创建千万次对象。但我们这千万的人达到目的真正 需要不是这个对象,而是需要这个对象的功能。
总结:当一个功能性的类,被多个对象共享使用方法时候,为了避免浪费内存资源和频繁的创建对象。需要将这个类单例化。
02?单例的设计过程
构造方法私有化
为了保证永远在内存中只有一个对象,那么不让其他类创建对象。我们需要设置创建的权限,只有类自己有权限创建对象。
private SingleTon(){}
内部创建对象
这时候问题来了,只有类本身能创建对象时候,我们应该在哪里创建呢我们复习一下类结构:构造方法、 代码块、方法、属性。
构造方法:构造方法已经私有化,你内部方法就算new了对象。但是抱歉外界并不能调用执行构造方法,这个new并不能执行
代码块:代码块是能够在类初始化时候执行,但是问题来了,有蛋生没篮子接,外界并不能拿到地址 找到这个代码块中创建的对象
方法:这就是一个先有鸡还是先有蛋的问题了,外部创建对象也就用不到这个方法
private SingleTon(){}public SingleTon singleTon=new SingleTon();
创建和获取
但是问题来了,你创建了对象,对象的属性创建了对象,对象的属性创建了对象的属性创建了对象.............系统会执行这样的一个套娃。
堆内存中对象不断往里嵌套属性,栈内存中第一个对象开辟的构造方法栈还没执行完,就还得开辟一个 新的构造方法栈,这样堆内存中的方法栈不断堆积形成栈内存逸出错误。
我们需要告诉计算机,这个对象只需要一个就行了,哪里可以让一个类只有一个对象存在静态区。
我们需要为这个属性加一个static修饰符一是让他类初始化完成避免了套娃式的创建对象,二是静态的变量能够被外界得到。
但是这时候又有个问题:
SingleTon.singleTon=null
如果有用户篡改了这唯一的对象,那就不可行了,我们需要做一个简单的封装,只留一个后门让你过来取。
private SingleTon (){}private static SingleTon singleTon=new SingleTon();public static SingleTon getSingleTon(){? ?return singleTon; }
但是也请别忘了,java世界中我们可以开挂:我们可以通过反射来获取私有属性进行篡改 所以我们需要加个?nal 修饰让变量本身变得完全不可改。
方式一:
private SingleTon (){}private static final SingleTon singleTon=new SingleTon();public static SingleTon getSingleTon(){? ?return singleTon; }
这是单例的方法之一,这代码有一个问题。我业务执行的时候,可能不会用这个对象,但是这个对象就 是初始化之后在内存中凉着;而且如果一个系统有千万个单例呢,系统初始化的时候,所有单例对象跟着 初始化这种服务器的压力不敢想象。
所以我们可以设置一个机制,等我们需要他的时候在创建他方式二。这时候大家跟我想一个问题,如果在多线程情况下会发生什么呢?
方式二:
private SingleTon(){}private static SingleTon ?singleTon;public static ?SingleTon getSingleTon(){? ?if (singleTon==null){ ? ? ? ?singleTon= new SingleTon(); ? ?} }
假如有A B两个线程:正常情况 A判断无 A创建 A创建完成 B判断直接使用
但是如果A创建的时候 B发现对象还没有创建也创建了呢
这就是一个原子性没有达到多线程并发所导致的安全性问题
所以为了防止多线程带来的问题我们应该给方法加个锁,加锁期间其他线程不能使用方法
方式三:
//防止高并发private SingleTon(){}private static SingleTon ?singleTon;public static ?synchronized SingleTon getSingleTon(){? ?if (singleTon==null){ ? ? ? ?SingleTon= new singleTon(); ? ?} }
但是还有一个问题:这个这个方法内部会执行很多其他事情,而这些事情多线程环境并不会导致问题。
这时候其他线程会没必要的常时间等待所以我们应该局部加锁(加锁中我们应该尽可能避免一个线程长时间持有锁)
方式四:
private SingleTon(){}private static SingleTon ?singleTon;public static ? SingleTon getSingleTon(){? ?if (singleTon==null){? ? ? ?synchronized(Singleton.class){? ? ? ? ? ?//我们判断之后立刻锁住这个类 ? ? ? ? ? ?//为了防止锁住之前已经有人先入创建了对象,再进行一次判断 ? ? ? ? ? ?if(singleTon == null){ ? ? ? ? ? ? ? ?singleTon= new Singleton() ? ? ? ? ? ?} ? ? ? ?} ? ?} }
这时候其实还有最后一个问题:
single=new Singleton(); 这行代码它执行了不止一步,分为三步
分配内存空间
调用构造方法,加载对象
这时候jvm本身有个特性为指令重排,就是这三个指令有可能会被打乱执行为了提高性能编译器和处理器常常会对既定代码执行顺序进行指令重排。
这时候大家跟我想一想:如果1执行了3比2先执行。执行完3准备执行2之前,另一个线程判断这个变量 有地址引用哦!非空的我直接拿去用啦,使用时候发现这是一个空壳,会爆出空指针异常。
所以我们需要让jvm不要执行指令重排 java 提供了volatile 关键字volatile 修饰变量有两个特性:1.禁止指令重排2.保证变量可见性第一个特性能够帮助我们解决指令重排问题:
方式五:
private SingleTon(){}private static volatile SingleTon ?singleTon;public static ? SingleTon getSingleTon(){? ?if (singleTon==null){? ? ? ?synchronized(Singleton.class){? ? ? ? ? ?if(singleTon == null){ ? ? ? ? ? ? ? ?singleTon= new Singleton() ? ? ? ? ? ?} ? ? ? ?} ? ?} }
总结:
方式一:在属性中创建对象,static修饰防止多次执行构造方法?anl和private修饰防止被修改,留一个static 的得到方法
方式二:增加一个懒加载机制,等需要的时候,对象如果没有再创建
方式三:为了防止高并发 给方法加锁
方式四:局部加锁只给赋值过程时候加锁方式五volatile 修饰变量防止指令重排
本文为原创
作者:杨龙
标签:错误 变量 堆内存 嵌套 注意 操作 准备 情况 多线程
原文地址:https://blog.51cto.com/13409950/2541778