java并发基础(六)--- 活跃性、性能与可伸缩性
《java并发编程实战》的第9章主要介绍GUI编程,在实际开发中实在很少见到,所以这一章的笔记暂时先放一放,从第10章开始到第12章是第三部分,也就是活跃性、性能、与测试,这部分的知识偏理论多一些,但是尽量能用代码讲明白的问题就不用文字,话不多说,进入正题。
一、死锁
在学习java基础的时候就听老师讲过“哲学家就餐”的例子,时间久了具体是怎么回事也容易忘,这里重新整理下。5个哲学家去吃中餐,坐在一张圆桌旁,他们有5根筷子(不是5双),并且每两个人中间放一根筷子,每个人需要一双筷子才能吃到东西,如果每个人都立即抓住自己左边的筷子,并等待自己右边的筷子空出来,同时又不肯放弃自己已经拿到的筷子,那么每个人都会饿死。在java中就是,线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么会产生死锁,总结一下,也就是多个线程由于存在环路的锁依赖关系而永远等待下去,所以会产生死锁,而java程序是无法从死锁中恢复过来的,所以开发时要尤其注意。
//容易发生死锁
public class LeftRightDeadLock{
private final Object left = new Object();
private final Object right = new Object(); public void leftRight(){
synchronized(left){
synchronized (right) {
System.out.println("left-right");
}
}
} public void rightLeft(){
synchronized (right) {
synchronized (left) {
System.out.println("right-left");
}
}
}
}
上面这种形式是最简单的死锁,叫锁顺序死锁,它发生死锁的原因是:两个线程试图以不同顺序访问相同的锁。反过来,也就是说,如果所有线程以固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁。但是在实际开发中几乎不可能实现。
有些时候死锁并不那么容易被发现,如下面代码,它将资金从一个账户转入另一个账户,在开始转账之前,首先要获得这两个Account对象的锁,以确保通过原子方式来更新两个账户中的余额,看上去没什么毛病,但是如果有两个线程同时调用transferMoney,其中一个线程从X向Y转账,另一个线程从Y向X转账,那么就可能发生死锁。
//A线程:transferMoney(myAccount,yourAccount,10);
//B线程:transferMoney(yourAccount,myAccount,20);
public void transferMoney(Account fromAccount,
Account toAccount,
Amount amount) throws InsufficientFundsException{
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.getBalance().compareTo(account) < 0) {//账户余额不能为0
throw new InsufficientFundsException();
}else {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
}
前面提到过,如果能让线程访问锁的顺序一致,可以避免锁顺序死锁,可以通过两个账户的hashCode判断,修改代码:
private static final Object tieLock = new Object(); public void transferMoney(Account fromAccount,
Account toAccount,
Amount amount) throws InsufficientFundsException{
class Helper{
public void transfer() throws InsufficientFundsException{
if (fromAccount.getBalance().compareTo(account) < 0) {//账户余额不能为0
throw new InsufficientFundsException();
}else {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
} int fromHash = System.identityHashCode(fromAccount);
int toHash = System.identityHashCode(toAccount); if (fromHash < toHash) {
synchronized (fromAccount) {
synchronized (toAccount) {
new Helper().transfer();
}
}
}else if (fromHash > toHash) {
synchronized (toAccount) {
synchronized (fromAccount) {
new Helper().transfer();
}
}
}else {
synchronized (tieLock) {
synchronized (fromAccount) {
synchronized (toAccount) {
new Helper().transfer();
}
}
}
}
}
这样就避免了上面提到的两个账户互相转账的问题,两个对象的hashCode如果相同,则又添加了一个锁,保证每次只有一个线程以未知顺序访问锁。你可能会问,为什么不直接在最外面再加一层锁呢,把hashCode的判断直接省去,肯定不能这样做,如果这样,那么转账就成了串行的了,这样毫无并发可言,好在System.identityHashCode发生哈希码相同的时候非常少。增加了代码,至少解决了问题,这样,这个方法至少能用了。
在大型网站中每天可能要执行数十亿次获取锁和释放锁的操作,只要有一次发生错误产生死锁,程序都会蹦掉,而且,有时候应用程序即使通过了压力测试也不可能找到所有潜在的死锁。很多潜在的死锁比上面的例子更加隐晦,这里就不写例子了,那么有什么最好的解决办法让我们尽量的避免锁顺序死锁呢,答案是在程序中始终使用开放调用。
什么是开放调用,我自己的理解是,在方法中调用某个外部方法时,没有持有任何锁,也就是说,锁用完了,赶紧释放掉。这也就是为什么不提倡直接用synchronized直接修饰方法的最重要的原因。我想获得哪个锁,我获得,用完了,赶紧释放掉,哪怕过几行代码我还要用到这个锁,我也不会多占用这个锁一秒钟。这个只能说是一个良好的编程习惯,但它可以尽量规避锁顺序死锁。
二、性能与可伸缩性
1、系统为什么要做成分布式
程序的性能由多个指标来衡量,比如服务时间、延迟时间、吞吐率、效率、可伸缩性以及容量等。服务时间和等待时间指某个任务需要多快才能完成,吞吐量指在计算资源一定的情况下,能完成多少工作。可伸缩性指的是:当增加计算资源时(例如CPU、内存、存储容量或I/O带宽),程序的吞吐量或者处理能力相应地增加。
性能的多快和多少是完全独立的,有时是互相矛盾的,我们熟悉的mvc三层结构是彼此独立的,并且可能由不同系统处理,这个例子很好的说明了提高可伸缩性通常会造成性能损失的原因,如果三层在单个系统中,处理第一个请求的时候其性能肯定高于将程序分成多层并将不同层次分布到多个系统时的性能。(LZ在面试时曾被问到具体说说是哪里带来了延迟,LZ当时只回答是网络通信,系统调用、数据复制之间的延迟,结果面试官很不满意,一再追问,大家可以自行百度,当做一个面试题记下来,预防面试官为难。)然而,当这种单一系统到达自身处理能力的极限时,会遇到一个很严重的问题:要进一步处理大量请求会非常难,因此,通常会接受每个请求执行更长时间或者消耗更多资源来换取更高的负载,这也就是为什么系统分布式的原因。
2、线程带来的问题
(1)上下文切换
如果可运行的线程数大于CPU数量,那么OS会将某个正在运行的线程调度出来,从而使其他线程能够使用CPU,这将导致一次上下文切换,也就带来一些开销。
(2)锁竞争
当线程由于等待某个锁而被阻塞时,JVM通常会将这个线程挂起,并允许它被交换出去,这也可能导致上下文切换。
3、减少锁竞争
通过上面介绍,串行操作降低可伸缩性,上下文切换降低性能,锁竞争会同时导致这两个问题,因此减少锁竞争能提高性能和可伸缩性。在并发程序中,对可伸缩性最主要威胁就是独占方式的资源锁。那怎么做呢,上面提到过了,开放调用,用完锁,赶紧释放,还有就是减少锁的粒度:锁分解和锁分段。这块内容LZ打算单独写一份笔记。
这块内容理论的东西很多,很多东西很难展开介绍,先看下这几个点吧,其实所有的知识点都指向如何避免锁竞争这个事情上,开放调用是一个办法,但是作用没有锁分段大。LZ也是刚开始用博客记录一些新知识,可能很多时候写东西写不到重点,我自己也尽量避免这个问题,慢慢来吧,很羡慕面对新知识能一下子抓住重点的大神,LZ只是一个想往上再走一步的菜鸟,各位多提意见,感谢大家。
java并发基础(六)--- 活跃性、性能与可伸缩性的更多相关文章
- java并发编程(4)性能与可伸缩性
性能与可伸缩性 一.Amdahl定律 1.问题和资源的关系 在某些问题中,资源越多解决速度越快:而有些问题则相反: 注意:每个程序中必然有串行的部分,而合理的分析出串行和并行的部分对程序的影响极大:串 ...
- java并发基础(二)
<java并发编程实战>终于读完4-7章了,感触很深,但是有些东西还没有吃透,先把已经理解的整理一下.java并发基础(一)是对前3章的总结.这里总结一下第4.5章的东西. 一.java监 ...
- Java 并发基础
Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...
- java并发基础及原理
java并发基础知识导图 一 java线程用法 1.1 线程使用方式 1.1.1 继承Thread类 继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继 ...
- 【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!
本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.欢迎 Sta ...
- java并发基础(五)--- 线程池的使用
第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...
- Java并发基础概念
Java并发基础概念 线程和进程 线程和进程都能实现并发,在java编程领域,线程是实现并发的主要方式 每个进程都有独立的运行环境,内存空间.进程的通信需要通过,pipline或者socket 线程共 ...
- 【Java并发基础】安全性、活跃性与性能问题
前言 Java的多线程是一把双刃剑,使用好它可以使我们的程序更高效,但是出现并发问题时,我们的程序将会变得非常糟糕.并发编程中需要注意三方面的问题,分别是安全性.活跃性和性能问题. 安全性问题 我们经 ...
- Java并发基础:进程和线程之由来
转载自:http://www.cnblogs.com/dolphin0520/p/3910667.html 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程. ...
随机推荐
- 20155318 2016-2017-2 《Java程序设计》第五周学习总结
20155318 2016-2017-2 <Java程序设计>第五周学习总结 教材学习内容总结 try...catch 键盘输入利用java.util.Scanner,Scanner 名 ...
- 第12月第26天 swift 下划线
1. The _ is used to define that the parameter is not named If you have multiple _ it states that you ...
- 第10月第13天 xcode ipa
1. xcodebuild -exportArchive -exportFormat ipa -archivePath RongChatRoomDemo\ 17-7-13\ 下午4.04.xcarch ...
- 洛谷 P3320: bzoj 3991: LOJ 2182: [SDOI2015]寻宝游戏
题目传送门:LOJ #2182. 题意简述: 一棵 \(n\) 个节点的树,边有边权. 每个点可能是关键点,每次操作改变一个点是否是关键点. 求所有关键点形成的极小联通子树的边权和的两倍. 题解: 有 ...
- 初始ASP.NET数据控件【续 ListView】
ListView控件 ListView控件可以用来显示数据,它还提供编辑,删除,插入,分页与排序等功能.ListView是GridView与DataList的融合体,它具有GridView控件编辑 ...
- sublime sftp 打开远程文件夹
2014-04-29 13:19:09 总结: 本文介绍两种方法,推荐第二种方法(samba+windows映射) 先贴出sublime打开远程(Linux)目录所需的配置文件(sublime是通过s ...
- 微信小程序入门与实战
1. 备注:并不是真的不需要下载,只是下载的包小于1MB,给人的感觉像是不用下载 2. 3. 理论上:同一级可以有无限个,纵向只能有五级 目前小程序分包大小有以下限制: 整个小程序所有分包大小不超过 ...
- .NetCore 下使用多个DbContext
一个项目中使用多个DbContext 或者种数据库的多个DbContext 业务需要 单个DbContext使用不需要给出说明 1.dotnet ef migrations add migration ...
- 【LOJ】#2527. 「HAOI2018」染色
题解 简单容斥题 至少选了\(k\)个颜色恰好出现\(S\)次方案数是 \(F[k] = \binom{M}{k} \frac{N!}{(S!)^{k}(N - i * S)!}(M - k)^{N ...
- 为什么sql里面not in后面的子查询如果有记录为NULL的,主查询就查不到记录
为什么sql里面not in后面的子查询如果有记录为NULL的,主查询就查不到记录???原因很简单: SELECT * FROM dbo.TableA AS a WHERE a.id NOT IN ( ...