【Java-基础系列】Synchronized关键字

初识

synchronize

翻译——同步(cause to occur or operate at the same time or rate.)

一般用于多线程操作,保证操作的同步性

消除线程切换时导致的共享内存的内容不可预测性

典型应用

单例模式的double-check

MacGesture MacGesture, Today at 12.45.47 AM

题外话:单例模式很容易引发内存泄露,在项目中要少用;

另外单例模式的实现形式中,使用enum枚举是比较简洁的形式

常见使用形式

  • 锁方法

    MacGesture MacGesture, Today at 12.16.25 AM

  • 锁代码段

    • 用this关键字 sychronized(this)

    MacGesture MacGesture, Today at 12.21.38 AM

    • 用锁对象 sychronized(lockObjct)

    MacGesture MacGesture, Today at 12.17.56 AM

官方教程

在Oracle的Java 文档里头有对Synchronization的详细解释,分为如下五部分展开:

  1. 线程接口(Thread Interference)

    【Java-揭面系列】Synchronized关键字.md Typora, Today at 12.14.14 AM

    不同线程对同一份数据的操作如果是穿插进行的,会导致不可预料的结果,这主要是因为CPU会让多个线程分片执行,各个线程对此数据的操作也是互不可知的,这样就很容易引发内存不按照预期改变的情况

  2. 内存连续性错误(Memory Consistency Errors)

    例子如上图,若三步操作不仅由一个线程执行,就很有可能导致打印出的并不是1而是0

  3. 同步方法(Synchronized Methods)

    • Java中在方法体上添加synchronized关键字,能够保证同一时间仅有一个线程在调用某个对象的该方法;
    • 同时Java还会为该方法在与对象之间建立一个happens-before关系,保证对象的该状态对所有线程都是可见的
  1. 固有锁与同步(Intrinsic Locks and Synchronization)

    MacGesture MacGesture, Today at 12.16.25 AM

    当一个线程调用synchronized方法时,它会自动获取该方法对象的固有锁,并在方法返回时释放它。即使返回是由未捕获的异常引起的,也会释放锁。

    MacGesture MacGesture, Today at 12.16.56 AM

    直接使用this当做锁的关键字时,改对象的所有使用this当关键字的操作都必须保证同时只有一个线程访问,这样做虽然简便,但时间上有效率浪费,因为该对象的所有操作并不一定要保持完全同步,👇的代码段就解决了这一问题:

    通过细粒度的锁来提高并发性:

    MacGesture MacGesture, Today at 12.22.45 AM

    MsLunch有两个实例字段,c1并且c2从不一起使用。这些字段的所有更新必须同步,但是没有理由阻止c1的更新与c2的更新进行交错 - 这样做会通过创建不必要的阻止来降低并发性。而不是使用同步方法或以其他方式使用与之关联的锁this

  2. 原子操作(Atomic Access )

    原子操作保证了操作的完整性:要么不执行,执行的话就一定要完全执行(即使虚拟机GC也不能打断)

    MacGesture MacGesture, Today at 12.35.40 AM

    第一条指的是我们可以使用Java内附的AtomicIntegerAtomicBoolean等变量来直接利用这一特性

    第二条引出了另一个关键字volatile,这实际上需要另起一篇进行介绍了

总结

synchronize在解决多线程编程问题时有奇效,但有一定的效率牺牲

使用时应尽量减小锁定的范围

对并发性有要求的地方需要注意看是否有细化锁粒度的必要

推荐阅读

【Java】神奇的内存占用大户FinalizerReference

问题起因

在解决应用内存占用久高不下的问题时,常常会发现排名第一的是一个叫做FinalizerReference的东西

下图是项目中内存快照在AndroidStudio2.4中打开看到的样子

可以看到,占用排名第一的类FinalizerReference占用了59M之巨(内存快照前有使用AS强制GC多次)

这就很让人好奇了

FinalizerReference是什么?

我们来看源码:

https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/lang/ref/FinalizerReference.java

在讲Java 虚拟机 JVM垃圾回收机制时会涉及到一个概念,即finalize方法,这是Sun公司在Java设计之初,为照顾c++程序员有写析构函数的习惯而做的一种妥协

在《深入理解Java虚拟机》一书中,讲到GC在最终回收对象之前,会去检查对象是否override了finalize方法,如果有覆盖此方法,对象就会被添加到finalize执行队列中,该书中没有指明是什么队列,实际上finalize执行队列就是我们这里讨论的FinalizerReference(书中还有提到finalize方法只会执行一次,并且有超时保护,因与本文主题无关,不做展开讨论)

也就是说,有重写Java根基类Object的finalize方法的所有对象,在被最终被GC回收之前,都会被添加到这个队列中,等待被执行

为什么不能被GC

这里要谈到JVM设计的一个基本标准

GC的finalize执行队列会在一个单独的守护线程中运行,这一线程的优先级极低;

一旦用户创建的对象速度过快,含finalize的对象速度快于finalize队列移除各元素的速度,FinalizerReference就会越来越大,而它获取CPU时间又少的可怜

测试中发现,1~2h过去后,FinalizerReference的大小并没有按照预计的回落到较低水准

这就迫使我们一定要解决这一问题,否则,随着应用开辟的内存越来越大,很容易引发喜闻乐见的OutOfMemoryError

如何解决

  1. 不要重写finalize()方法,实在要释放资源,请到其它destroy一类函数中处理

实际上Java库中不少方法都有重写finalize方法(如Input/outputStream、Canvas、Paint)

这就引出了第二条指导原则:

  1. 重复利用资源(避免反复创建)

  2. 杀器FinalizerHelper

    • 此工具来源于公司同事的技术分享
    • 指导思想:在场景退出的安全节点,利用反射强制调用FinalizerReference的remove方法,将对象从队列中移除掉
    • 对具体实施细节的同学联系我

【Alibaba-Java开发手册】-提要

旨在对手册进行提要
关注点: 编程规约

手册源文件点击下载

EverNote笔记链接(内含XMind思维导图源文件)


#导图
Alibaba Java overview.png


文本

Alibaba Java 开发手册

1 编程规约
1.1 命名规约
1.1.1 8. POJO类中的布尔型变量不要加is,否则会引起序列化错误
1.2 常量定义
1.2.1 1. 不允许代码中出现任何魔法数
1.2.1.1 (即硬编码变量)
1.2.2 3. 不要再一个常量类中维护所以常量,应按功能归类维护
1.2.3 4. 常量复用有五个层次
1.2.3.1 跨应用、应用内、子工程、包内、类内
1.3 格式规约
1.3.1 一系列Google Java编码规则,不赘述
1.4 OOP规约
1.4.1 1. 避免通过对象引用static域
1.4.2 2. 不可使用 Deprecated方法;自己做弃用注解时也应该 注明替代的新API
1.4.3 10. 序列化新增属性时不要修改serialVersionUID字段;仅在完全不兼容升级避免反序列化混乱时修改
1.4.4 15. 方法排序: public/ protected > private > getter/setter ——为维护者关心的方法展示于前
1.4.5 16. setter/getter 确定为仅有的set get方法,勿要放入业务逻辑
1.4.6 17. 循环体内,字符串联接方式,使用StringBuilder的append方法扩展——String 操作编译后会生成count 个StringBuilder对象,影响程序性能
1.4.7 18. final可提高程序响应效率
1.4.8 20. 访问控制从严(private、protected、public)
1.5 集合处理
1.5.1 1. hashCode equals
1.5.1.1 重写equals时必须重写hashCode
1.5.1.2 Set存储的对象必须重写
1.5.1.3 用作Map的键时必须重写
1.5.2 9. 集合初始化时尽量指定大小
1.5.2.1 注:出于优化内存占用、响应速度考虑;集合默认size为10
1.5.3 10. Map 类集合 K/V 存储null可行性问题
1.5.3.1 HashMap KV均可为null
1.5.3.2 TreeMap仅V可为null
1.5.3.3 其它Map KV一律不可为null
1.5.4 11. 合理利用好集合的有序性和稳定性,避免负面影响
1.6 并发处理
1.6.1 2. 创建线程、线程池时指定有意义的名称,便于出错时回溯
1.6.2 4. 线程池不允许使用Executors创建,而要使用ThreadPoolExecutor
1.6.2.1 Executor创建的几个类型都有大量请求或线程堆积导致OOM的可能
1.6.3 5. SimpleDateFormat 是线程不安全类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类
1.6.4 6. 高并发时的同步调用注意锁的性能损耗
1.6.5 10. CountDownLatch异步转同步操作时,每个线程退出前必须调用countDown,避免await超时
1.6.6 11. 避免Random实例被多线程使用,竞争同一seed会导致性能下降;(实例包括Random、Math.random())
1.6.7 14. HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,开发中注意规避
1.7 控制语句
1.7.1 3. if-else 勿超过3层,超过时使用状态设计模式
1.7.2 5. 循环体中考虑性能问题,避免不必要的语句、避免不必要的try-catch
1.7.3 7-8. 注意参数检验的必要性,尽可能提升性能
2 日志
3 MySQL
4 工程规约
5 安全规约

LogFoQATest

要求

  1. 判断手机是否存在指定TXT文件。如果存在开始打印log信息进入TXT文件里面。
  2. 加入是否将ORG设置默认的log。
  3. 是否调起menu的log信息。
  4. 打印menu是否拉去广告的log信息,
  5. 其他网络请求。

实现记录

所以日志以 QATestInfo 为log的TAG信息

  1. 默认包含此功能;

  2. SetDefaultHomeOK

  3. InvokeMenuPopUp

  4. RequestMenuAD

  5. 可能较耗内存的场景:

    RequestNearbyApp
    RequestCommonTool

    ImageLoaderLoadImageFromNet
    ImageLoaderLoadAdmobImageCore
    VolleyDisplayImage
    VolleyDecodeImage
    VolleyDecodeImageFix

Java进阶——接口优于抽象类

本篇系Java进阶系列 篇4.18,介绍接口相较于抽象类的优点

跑题1——一直牢记的一点“100%的坚持比百分之九十几的坚持更容易”又一次体会到,上周跟随部门团建出去旅游,一旦停更就是将近一周,希望能把原则秉持下来

跑题2——今天看了new iPhone的发布会,依旧很精致,不知何时Android能有赶上iPhone水准的机器面世

两者区别

抽象类可以包含部分实现,接口不可以(Java8接口中可以定义默认方法,但不推荐如此使用接口)

为了实现抽象类定义的类型,必须extends 抽象类,限制了其可扩展性

接口的优势

  • 已有类可以很容易被更新,以实现新接口
  • 接口是定义mixin(像中文“迷信”,实则取自此词,代表混合类型)的理想选择
  • 允许我们构造非层次结构的类型框架(就像我们每个人身怀多技一样,每个类也可能有多种特性)
  • 使得增强类的功能更加安全

pay attention

  • 公共接口一旦被公开,修改起来就极其麻烦,所谓牵一发而动全身,所以设计接口时要谨慎;

    实际项目中发现这个点其实是一个经验问题,又要最小化接口,又要把接口设计得符合SOLID设计模式五大原则,就要求我们在实际工作中训练好这个技能,把握好接口设计的度

  • 抽象类也并非一无是处,它的改变就比接口要简易的多,实际使用时也是体验很好的一种设计方法,不过抽象类的使用也要有所衡量,只有在确实是属于一个类别的类时才好去使用抽象类,否则就违背了我们的编程原则

Java进阶——接口优于抽象类

摘要与关键字

继承很强大,但也有问题

确实是is-a关系时才使用继承

如果处在不同的包中,并且SuperClass不是为继承设计的,这时会导致脆弱性

可以使用复合和转发机制来代替继承来避免这种脆弱性

关键字:

继承、复合、装饰者模式、包装类、转发

keywords:

继承(inheritance)、复合(composition)、装饰者模式(decorator)、包装类(WrapperClass)、转发(forwarding)

为叙述方便,下文以CC代表子类,SC代表超类,也就是父类

问题体现

继承打破了封装性

CC依赖于SC的实现细节,在SC更新换代时,CC会受影响,严重的会导致错误,为了避免出现错误,时常要做的事情是跟着SC的更新而更新CC

其实你可以直接使用复合来避免这一问题出现——也就是,在原来的CC中添加一个SC的实例

这样新的类中可以直接使用SC的方法逻辑,而不用担心SC更新时要升级API的问题(概念:转发

这样的类也被称为包装类

这也引出了设计模式中的Decorator模式概念(装饰者模式

强制使用的弊端

  • 暴露不必要的SC细节
  • 永远地限制了CC的性能(使用复合模式则可以在需要时更换转发的具体实现)
  • 导致语义混淆

Java进阶--for-each与传统for

结论

for-each 相较于 传统的 for-i更优

自己编写的类在代表一组元素时,即使不实现Collection也要让它实现Iterable

体现

for-each循环在预防bug方面与传统for循环有着无可比拟的优势

  • 传统for
  • for-each

实现方式

implement Iterable接口,示例:

Java进阶-- volatile关键字

volatile含有部分synchronized的特性,效率更高,但使用时需要注意细节,不然容易出错

是什么

Java中被volatile修饰的变量,每次读写都会直接使用“内存”介质,而不是使用CPU缓存

音 — UK /ˈvɒl.ə.taɪl/ US /ˈvɑː.lə.t̬əl/

英 — likely to change suddenly and unexpectedly or suddenly become violent or angry

中 — 易变的

特性

  • 保证变量可见性
  • 禁止指令进行重排序

基础概念:可见性、有序性、原子性

  1. 可见性:对所有线程来说,任一线程对变量的改动都立即可见
  2. 有序性:代码可能会被编译器混编,有序性即指代码按顺序执行
  3. 原子性:所有操作不会被打断,谁也不行(除非断电)

实际上我们对变量的修改,虽然可能Java就一行代码(如 y=x),但被解释成汇编语言会变成多行(y=x就需要进行两步操作:读x值,写入y所在内存,学过汇编的同学应该比较清楚)

与synchronized的区别

按字面理解,volatile关键字只是标记变量为易变的,并不能保证对变量的所有操作都是同步的,即不能保证具有synchronized一样的效果

实际上,对volatile修饰的变量的读/写操作是可以保证其原子性的,但还要注意一个问题,即同时写变量的线程可能不止一个,如此一来,其中部分的写操作就有可能丢失

听起来好像volatile使用起来很危险,但它也是有它的用途的

用法

  • 对于写不依赖历史状态的变量,可以考虑使用volatile修饰字代替synchronized来提高程序效率
  • 保证代码的有序性:volatile变量的读写操作之前的代码,编译器是不会进行重排序的

小结

在volatile满足不了要求的场景可以考虑使用synchronized、lock、Atomic变量来保证特性

对变量的写不依赖历史状态的变量使用volatile修饰可以提高效率

对于需要保证部分程序语句有序性的场景可以考虑使用volatile代替锁定整个代码块,提高效率

Java进阶-- 以函数对象代表策略

摘要与关键字

介绍设计模式中的策略模式(strategy)

C++、C语言中的函数指针在Java中的替代形式

面向接口编程

keywords:

策略、函数对象、面向接口

出发点

函数指针,或是实现代理模式、lambda表达式

###


Powered by KyleCe

Copyright © 2015 - 2019 KyleCe All Rights Reserved.

访客数 : | 访问量 :