《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并发基础(六)--- 活跃性、性能与可伸缩性的更多相关文章

  1. java并发编程(4)性能与可伸缩性

    性能与可伸缩性 一.Amdahl定律 1.问题和资源的关系 在某些问题中,资源越多解决速度越快:而有些问题则相反: 注意:每个程序中必然有串行的部分,而合理的分析出串行和并行的部分对程序的影响极大:串 ...

  2. java并发基础(二)

    <java并发编程实战>终于读完4-7章了,感触很深,但是有些东西还没有吃透,先把已经理解的整理一下.java并发基础(一)是对前3章的总结.这里总结一下第4.5章的东西. 一.java监 ...

  3. Java 并发基础

    Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...

  4. java并发基础及原理

    java并发基础知识导图   一 java线程用法 1.1 线程使用方式 1.1.1 继承Thread类 继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继 ...

  5. 【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!

    本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.欢迎 Sta ...

  6. java并发基础(五)--- 线程池的使用

    第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...

  7. Java并发基础概念

    Java并发基础概念 线程和进程 线程和进程都能实现并发,在java编程领域,线程是实现并发的主要方式 每个进程都有独立的运行环境,内存空间.进程的通信需要通过,pipline或者socket 线程共 ...

  8. 【Java并发基础】安全性、活跃性与性能问题

    前言 Java的多线程是一把双刃剑,使用好它可以使我们的程序更高效,但是出现并发问题时,我们的程序将会变得非常糟糕.并发编程中需要注意三方面的问题,分别是安全性.活跃性和性能问题. 安全性问题 我们经 ...

  9. Java并发基础:进程和线程之由来

    转载自:http://www.cnblogs.com/dolphin0520/p/3910667.html 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程. ...

随机推荐

  1. html js点击按钮滚动跳转定位到页面指定位置(DIV)的方法代码

    一:通过html锚点实现滚动定位到页面指定位置(DIV):    如果我们要点击实现跳转的地方是一个html锚点,也就是点击一个A标签超链接实现跳转,可以把A标签的href属性直接指向跳转指定位置的d ...

  2. git 学习小记之记住https方式推送密码

    昨天刚刚学了点git基础操作,但是不幸的是Git@OSC给出公告说尽量使用 https 进行操作.可是在用 https 进行 push 时,都需要输入帐号和密码. 各种百度谷歌之后在Git@OSC官网 ...

  3. HDU 1241 Oil Deposits DFS搜索题

    题目大意:给你一个m*n的矩阵,里面有两种符号,一种是 @ 表示这个位置有油田,另一种是 * 表示这个位置没有油田,现在规定相邻的任意块油田只算一块油田,这里的相邻包括上下左右以及斜的的四个方向相邻的 ...

  4. 洛谷4951 地震 bzoj1816扑克牌 洛谷3199最小圈 / 01分数规划

    洛谷4951 地震 #include<iostream> #include<cstdio> #include<algorithm> #define go(i,a,b ...

  5. Redis知识点总结

    1.单线程 单线程模型来处理客户端的请求,对读写等事件的相应是通过对epoll函数的包装来做到的,Redis的实际处理速度完全依靠主线程的执行效率. Epoll是Linux内核为处理大批量文件描述符而 ...

  6. flask基础之session原理详解(十)

    前言 flask_session是flask框架实现session功能的一个插件,用来替代flask自带的session实现机制,flask默认的session信息保存在cookie中,不够安全和灵活 ...

  7. Git 使用规范流程【转】

    转自:http://www.ruanyifeng.com/blog/2015/08/git-use-process.html 作者: 阮一峰 日期: 2015年8月 5日 团队开发中,遵循一个合理.清 ...

  8. 013_Mac OS X下应该如何卸载软件和安装应用软件

    一.Mac OS X下应该如何卸载软件 Mac OS X的软件安装方式有很多种,而软件卸载的情况也很不同.在Mac OS X拆除软件往往不是把软件拉到废止篓里那么简单.通常情况下要具体问题具体分析.无 ...

  9. nginx前后端分离路由配置

    参考链接: https://blog.csdn.net/qq_30021219/article/details/80901199

  10. maven-replacer-plugin 静态资源版本号解决方案(css/js等)

    本文介绍如何使用 maven 的 com.google.code.maven-replacer-plugin 插件来自动添加版本号,防止浏览器缓存. 目录 1.解决方案 2.原始文件和最终生成效果 3 ...