阿里开发规约之编程规约(4)
前言
哈喽,大家好,我是Java选手牛皮糖。本周也是个值得兴奋的日子,没有征兆的下起雪来了。下了一整天的雪,可惜是在上班,不然定要约上三五好友去搓上一顿火锅。吃着火锅唱着歌,赏着雪不要太爽。
正文
上回一块学习了项目中十分常用的集合处理等,那这回我们就一块来看看并发处理。
并发处理
1、 【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
说明:资源驱动类、工具类、单例工厂类都需要注意。
单例相关:
定义:单例类只允许一个实例存在。
适用场景:
- 需要生成唯一序列的环境
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 方便资源相互通信的环境。
项目中使用的场景:
- 工具类。
- 配置文件。
2、【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例:自定义线程工厂,并且根据外部特征进行分组,比如,来自同一机房的调用,把机房编号赋值给
whatFeatureOfGroup
public class UserThreadFactory implements ThreadFactory { private final String namePrefix;
private final AtomicInteger nextId = new AtomicInteger(1);
// 定义线程组名称,在利用 jstack 来排查问题时,非常有帮助
UserThreadFactory(String whatFeatureOfGroup) {namePrefix = "From UserThreadFactory's " + whatFeatureOfGroup + "-Worker-";
}
@Override
public Thread newThread(Runnable task) {String name = namePrefix + nextId.getAndIncrement(); Thread thread = new Thread(null, task, name, 0, false); System.out.println(thread.getName());
return thread;
} }
3、 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
创建和销毁线程是非常浪费资源的事情,内存资源对Java来说是十分重要的,因此在使用线程的时候让线程池帮助解决这个问题。
4、【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这 样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 - CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
Executors部分源码
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
5、【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,
必须加锁,或者使用 DateUtils 工具类。
正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){ @Overrideprotected DateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");
} };
static背景
static只能修饰内部类,且static在内存中只存储一份数据。被static修饰的内部类或变量是属于这个类的,而不是实例的。因此多线程情况下会存在数据不一致的问题。
SimpleDateFormat背景
SimpleDateFormat类中通过变量calendar存储时间,且SimpleDateFormat是线程不安全的类。如果使用static修饰SimpleDateFormat,则calendar将变成全局变量,则当多个线程同时进行SimpleDateFormat#parse、SimpleDateFormat#format方法时,就会出现线程不安全问题。
6、 【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用, 如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。 尽量在代理中使用 try-finally 块进行回收。
正例:
objectThreadLocal.set(userInfo); try {
// …
} finally { objectThreadLocal.remove();
}
ThreadLocal背景
ThreadLocal是解决线程不安全的一种方式。它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题,是属于线程的。当线程结束的时候,ThreadLocal也会被回收,如果线程被复用了,则ThreadLocal不会被回收就造成了内存泄露。
7、【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能 锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。最小加锁原则。
8. 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造 成死锁。
说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、 B、C,否则可能出现死锁。
9. 【强制】在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代 码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
说明一:如果在 lock 方法与 try 代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功 获取锁。
说明二:如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中,unlock 对未加锁的对象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出 IllegalMonitorStateException 异常。
说明三:在 Lock 对象的 lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。
正例:
Lock lock = new XxxLock(); // ...
lock.lock();
try {doSomething();
doOthers(); } finally {lock.unlock(); }
反例:
Lock lock = new XxxLock(); // ...
try {// 如果此处抛出异常,则直接执行 finally 代码块 doSomething();
// 无论加锁是否成功,finally 代码块都会执行 lock.lock();
doOthers();
} finally { lock.unlock();
}
10.【强制】在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否
持有锁。锁的释放规则与锁的阻塞等待方式相同。
说明:Lock 对象的 unlock 方法在执行时,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),如果
当前线程不持有锁,则抛出 IllegalMonitorStateException 异常。
正例
Lock lock = new XxxLock();
// ...
boolean isLocked = lock.tryLock(); if (isLocked) {try { doSomething();
doOthers(); } finally {lock.unlock(); }
}
11.【强制】并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。
乐观锁:每次写数据的时候,认为不会被别的线程操作,但是更新的时候会判断下数据有没有被改过。(适用于多读少写的场景)
悲观锁:每次写数据都认为会有别的线程操作,然后加锁。别的线程会一直被阻塞,直到拿到锁为止。(适用于一致性比较高的场景)
12.【强制】多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛 出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
总结
高并发场景一致是项目中的难点,同时也是面试中面试官最喜欢面的地方。上文中每条规则都可以延伸很多,限于篇幅,这里就不做过多的分析。后面抽时间在和大家深入分下高并发的场景。
阿里开发规约之编程规约(4)相关推荐
- 阿里巴巴开发规约之编程规约(2)
前言 碎碎念 最近快过年了,无心工作,只想回家过年.听着外面的鸟叫,明明是冬天却感觉像是春天才会出现的鸟儿.看来是小鸟说回家过年,回家过年了.哈哈哈,废话不多说,咱么继续书接上回. 正文 上回分享了规 ...
- 阿里Java开发手册之编程规约
阿里Java开发手册之编程规约 对于程序员来说,编程规范可以养成良好的编程习惯,提高代码质量,降低沟通成本.就在2月9号,阿里出了一份Java开发手册(正式版),分为编程规约,异常日志,MySQL规约 ...
- 白话阿里巴巴Java开发手册(编程规约)
本文欢迎转载,转载请注明原文链接,并附作者个人信息李艳鹏. 研发流程管理 最近,阿里巴巴发布了<阿里巴巴Java开发手册>,总结了阿里人多年一线实战中积累的研发流程规范,这些流程规范在一定 ...
- 阿里巴巴Java开发手册-编程规约
本文转自:http://www.jianshu.com/p/bc8fed863eca?hmsr=toutiao.io&utm_medium=toutiao.io&utm_sour ...
- 【C#编程规范 一】编程规约(上)
编程规约是比较重要的部分,按照基础和高级,我分成了两篇来学习,上篇涉及到命名风格.常量定义.代码格式和OOP规约都是面向对象基础部分和一些通识命名规范. 命名风格 条目较多,所以使用金字塔的风格进行分 ...
- 【阿里巴巴Java编程规范学习 一】Java基本编程规约(上)
编程规约是比较重要的部分,按照基础和高级,我分成了两篇来学习,上篇涉及到命名风格.常量定义.代码格式和OOP规约都是面向对象基础部分和一些通识命名规范.红色加粗字体为自己可能会犯的错误以及不规范的地方 ...
- 阿里巴巴 Java 开发手册之编程规约(一)-------我的经验
阿里巴巴 Java 开发手册 一.编程规约 (一) 命名规约 1.[强制] 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束.(代码规范,易读) 反例: _name / __na ...
- 阿里云开源编程马拉松入围项目
在9月8号启动的 阿里云开源编程马拉松 至今已经吸引了100多个团队参赛.经过评委的初步筛选现有14个参赛项目入选.入选的项目团队/个人 将会在10月14日云栖大会现场进行路演,在大会现场颁奖并评选出 ...
- 《阿里巴巴开发手册》读书笔记-编程规约
Java编程规约 命名风格 常量定义 代码格式 OPP规约 日期时间 集合处理 并发处理 控制语句 注释规约 前后分离 其它注意 命名风格 类名使用UpperCamelCase风格,但下列情形除外: ...
- 阿里17实习生编程-数组四等分
阿里17实习生编程练习 亮题 对于一个长度为N的整型数组A, 数组里所有的数都是正整数,对于两个满足 0<=X <= Y <N的整数,A[X], A[X+1] - A[Y]构成A的一 ...
最新文章
- 怎么编写段错误(Segmentation fault)的程序
- 什么叫做石英表_石英表和机械表的区别是什么
- 仿微信公众号后台管理-自定义菜单
- 洛谷 P1352 没有上司的舞会(树形 DP)
- mysql删除新添加数据,MySQL添加、更新与删除数据
- 分类算法之朴素贝叶斯算法
- dataGruidView整行选中
- Python基础import导包问题
- c# 线程 WPF 进度百分比(菜鸟)
- html链接描述,HTML常用文本标记,超级链接和路径描述
- 2020年中国林业有害生物发生及防治面积统计情况,林业有害生物防控措施工作的开展刻不容缓「图」
- 【转】Laravel - 从百草园到三味书屋 From Apprentice To Artisan目录
- Canvas基础教程
- 生化环材CSDN文章索引
- 如何推广微信公众号 微信公众号推广技巧
- 用UML建模开发嵌入式软件
- 整理MAC下Eclipse的常用快捷键
- Windows10 部署Davinci开发环境
- Mybatis学习笔记(三)
- Canvas绘制任意正多边形