24 | 黑白灰,理解延迟分配的两面性
范学雷
该思维导图由 AI 生成,仅供参考
上一次,我们讨论了减少内存使用的两个大方向,减少实例数量和减少实例的尺寸。如果我们把时间的因素考虑在内,还有一些重要的技术,可以用来减少运行时的实例数量。其中,延迟分配是一个重要的思路。
延迟分配
在前面讨论怎么写声明的时候,为了避免初始化的遗漏或者不必要的代码重复,我们一般建议“声明时就初始化”。但是,如果初始化涉及的计算量比较大,占用的资源比较多或者占用的时间比较长,声明时就初始化的方案可能会占用不必要的资源,甚至成为软件的一个潜在安全问题。
这时候,我们就需要考虑延迟分配的方案了。也就是说,不到需要时候,不占用不必要的资源。
下面,我们通过一个例子来了解下什么是延迟分配,以及延迟分配的好处。
在 Java 核心类中,ArrayList 是一个可调整大小的列表,内部实现使用数组存储数据。它的优点是列表大小可调整,数组结构紧凑。列表大小可以预先确定,并且在大小不经常变化的情况下,ArrayList 要比 LinkedList 节省空间,所以是一个优先选项。
但是,一旦列表大小不能确定,或者列表大小经常变化,ArrayList 的内部数组就需要调整大小,这就需要内部分配新数组,废弃旧数组,并且把旧数组的数据拷贝到新数组。这时候,ArrayList 就不是一个好的选择了。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
延迟分配是一个重要的技术,可以用来减少运行时的实例数量,从而降低内存使用。通过延迟初始化,可以避免不必要的资源占用,提高性能。文章通过讨论ArrayList和HashMap的实现变化,以及延迟初始化的实现方法,阐述了延迟分配的两面性。延迟初始化可以避免内存资源的浪费,但需要考虑CPU效率和多线程安全等问题。总之,对于使用频率高的类的实现,微小的性能改进都可以带来巨大的实用价值。读者应该根据具体情况,具体分析,采用延迟初始化是否可以提高效率,然后再决定使用这种方案是否划算。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《代码精进之路》,新⼈⾸单¥59
《代码精进之路》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(14)
- 最新
- 精选
- yang1 通过采用java内存模型,保证多线程场景下共享资源的可见性 2使用局部变量,可以减少主存与线程内存的拷贝次数 3第一次是初始化,第二次是同步局部变量与属性变量的值,保持一致 4第一次检查是为了快速获取对象,第二次检查是为了防止对象未初始化,就是标准的double check 5是为了线程安全,同时高性能,锁范围更小化 6前者是线程安全,后者是非线程安全 7还是减少主存与线程内存值拷贝的开销 个人理解,如有误,敬请指正
作者回复: volatile的使用,需要一定程度的同步,也就是你说的拷贝开销。减少volatile变量的引用,可以提高效率。 恭喜你,这些Java的难点你掌握的很扎实!
2019-02-27343 - 梦醒时分我的思考: 1.volatile是用来保证变量的可见性的,这样其他线程才能及时看到变量的修改 2.为啥要使用temporaryMap变量,这里没有想明白 3.两次设置temporaryMap变量,目的是双重检查,防止进入同步代码块中,变量已被赋值了 4.同上 5.synchronized的使用是影响性能的,所有在使用它之前,先校验下是否需要进入同步块中 6.ConcurrentHashMap是线程安全的,而HashMap不是线程安全的 7.为啥使用temporaryMap.put不太清楚
作者回复: 关于temporaryMap的使用,请参考@yang的留言。
2019-02-276 - 拉可里啦作为类的全局变量而非静态变量,只能被类的实例所拥有,那么只有一个对象再操作这个全局变量,单线程操作这个变量不会有线程安全问题,多个线程同时操作这个变量有线程安全问题,是以对象为单位的。不知道我这样理解对不对,还请老师指点指点。
作者回复: 是的,是以对象为单位的。 所以,你看Java的代码里,synchronized(this), this指的就是一个具体的对象。同一个类,实例化的对象不同,也不需要同步。
2019-04-074 - Linuxer请问各位思考题中的volatile修饰后是不是就只能用concurrenthashmap?要不赋值给局部变量后主存和线程内存还是不同步
作者回复: volatile修饰符和使用concurrent hash map关系不大。volatile修饰的是标志符,不是标志符指向的内容。
2019-02-283 - Jxin1.类属性的调用和赋值全部走set和get方法。这种非静态且多次赋值的局部变量应该尽量避免。 2.带锁初始化操作应该上移到get方法。至少从函数功能来看,我认为初始化应该是属于get的。 3.我真不喜欢加锁和每次get都做判断,所以了解业务上下文,如果可以,我会直接给该属性做初始化。 3.如果了解完上下文我对这个集合或则散列表的大小能有一定把我,我会尝试给定一个合适的初始化大小。
作者回复: 👍都是很好的实践经验! 了解适用场景,是高效代码的关键。
2019-05-282 - 鱼筱雨范老师,我总是有个疑惑,举个例子 Map<String,String> m = new HashMap<>(); 在这段代码中,左边的泛型中会声明具体类型,而右边的泛型中往往是空的,而我在代码开发中右边的泛型都会和左边保持一致,这样做有什么问题吗?哪种更好一点?
作者回复: 现在推荐使用的模式是声明时指定类型,使用时让编译器来自动匹配。对于上面的代码,也就是左边声明具体类型,而右边使用空指示符。这样做的主要目的是简化编码,避免不必要的失误和检验。
2019-08-291 - aguan(^・ェ・^)老师,我有一个疑问,思考题的代码,在多线程的情况下,如果第16行用tempHashmap.put,是会出现空指针的。因为cpu指令重排序,当线程1在执行new map的时候,可能cpu先给temp分配内存空间,对象还没实例化,这时候另外一个线程在第一个if的时候发现temp不为空(因为有地址,但地址里并没有实例化对象),接着去执行16行的代码,会出现空指针问题吧
作者回复: tempHashmap是一个局部变量,不跨越线程。
2019-03-061 - 唐名之@yang回到第二点 使用局部变量,可以减少主存与线程内存的拷贝次数 这个点还是有点不明白能解释下嘛?2019-03-013
- Jxin采用了临时变量,对象是否就失去了可视性和有序性的特性?这样在new操作时,由于虚拟机的编译优化,cpu时间片切换时不是可能会出现空指针报错吗?2019-06-101
- aguan(^・ェ・^)恍然大悟,所以局部变量是解决双重检查重排序空指针问题的安全方法👍2019-03-071
收起评论