五通桥| 科尔沁左翼中旗| 三亚| 民和| 宁德| 建昌| 安乡| 方正| 石林| 清镇| 头屯河| 雷山| 茂名| 平远| 尚志| 民权| 珲春| 榆林| 清涧| 金佛山| 临清| 阎良| 江达| 康保| 金山屯| 覃塘| 云林| 右玉| 乡宁| 浠水| 乳源| 南皮| 南昌县| 淇县| 郴州| 齐齐哈尔| 凌源| 弋阳| 河北| 志丹| 洪湖| 嘉荫| 科尔沁左翼后旗| 宁海| 吴江| 隆回| 平阳| 木兰| 二连浩特| 府谷| 藤县| 甘洛| 麻栗坡| 合山| 金乡| 威信| 五华| 万全| 商城| 隆林| 惠农| 雷山| 耒阳| 扎兰屯| 余庆| 米林| 崇仁| 蕲春| 湘乡| 安义| 峰峰矿| 新竹市| 鄂温克族自治旗| 潮南| 安县| 新兴| 文昌| 民和| 丰镇| 西峡| 合阳| 阿克塞| 许昌| 于都| 兰坪| 新沂| 德惠| 行唐| 凤台| 海伦| 久治| 南澳| 美溪| 临川| 普宁| 灵山| 福州| 酉阳| 南江| 洪雅| 田阳| 海城| 政和| 岚皋| 商河| 广宗| 扬州| 赤城| 河间| 额济纳旗| 尚志| 天山天池| 玉田| 土默特左旗| 桦甸| 伊宁市| 贞丰| 望谟| 尖扎| 永德| 河南| 疏附| 张掖| 汉川| 海口| 蒙阴| 南漳| 临淄| 静海| 金塔| 百色| 项城| 眉山| 红安| 温宿| 抚州| 南海| 北仑| 富县| 吉安市| 青浦| 攸县| 乌拉特前旗| 会同| 浑源| 成都| 水城| 茂名| 淮阳| 武隆| 鹿泉| 大通| 秦安| 忻城| 海口| 梧州| 漳县| 周村| 镇康| 昌江| 达拉特旗| 建德| 大方| 吴桥| 南郑| 化州| 营山| 平房| 周口| 珲春| 南雄| 左贡| 天祝| 丹寨| 错那| 佛山| 定兴| 定兴| 舒兰| 哈巴河| 大城| 丹江口| 石楼| 察哈尔右翼前旗| 德惠| 怀远| 新和| 高青| 临漳| 千阳| 遵化| 砚山| 武进| 铜梁| 咸阳| 浦口| 合水| 新竹市| 灵武| 大关| 麻江| 衡山| 临西| 湘乡| 澜沧| 尼玛| 鲁山| 景宁| 洱源| 广州| 芷江| 谢家集| 武宁| 泸西| 定边| 平顺| 洋山港| 沙坪坝| 防城区| 台江| 扎囊| 重庆| 佳木斯| 日喀则| 小金| 托克逊| 新县| 清原| 佳县| 肇源| 利川| 扎囊| 互助| 宁津| 太湖| 桐梓| 吴中| 长子| 丹巴| 安图| 八达岭| 拉萨| 德格| 滴道| 施甸| 榆中| 铜鼓| 泸溪| 德江| 新津| 合水| 绿春| 仁布| 覃塘| 太谷| 阳城| 新青| 肃南| 稷山| 凤庆| 新民| 贵溪| 邵阳县| 博罗| 百家乐技巧
|
|
51CTO旗下网站
|
|
移动端
创建专栏

旺财和小强的三生三世

旺财和小强是线程池的两个线程, 他们经常做的工作就是对一个数加加减减,用人类的话来说就是存款,取款。

作者:刘欣|2019-01-18 15:26

 第一世

旺财和小强是线程池的两个线程, 他们经常做的工作就是对一个数加加减减,用人类的话来说就是存款,取款。

  1. public class Account{ 
  2.    private int balance; 
  3.    public synchronized void deposit(int amt){ 
  4.        balance += amt; 
  5.    } 
  6.  
  7.    public synchronized void withdraw(int amt){ 
  8.        if(balance >= amt){ 
  9.            balance -= amt; 
  10.        } 
  11.        throw new RuntimeException("insufficent blance"); 
  12.    } 

(友情提示,可左右滑动,下同)

每次进行存款,取款操作的时候,他们两个都需要获得一把锁,这样就能保证同一时刻只有一个人在修改,不会出乱子。

这一天,他们俩又遇到了一个叫做转账的操作:

  1. public void transfer(Account from,Account toint amt){ 
  2.    synchronized(from){ 
  3.        synchronized(to){ 
  4.            from.withdraw(amt); 
  5.            to.deposit(amt); 
  6.        } 
  7.    } 

旺财说:“这个程序员不错,考虑得挺周全。转账的时候把两个账户都锁住了,安全!”

小强说:“没错,执行吧。”

旺财这个线程从A向B转账 , 与此同时,小强从B向A转账

令旺财和小强没有想到的是,居然出现了死锁。

类似的事件发生不少, 线程池的线程用光了,Tomcat被迫重启,这个世界毁灭了。

第二世

新生代的旺财和小强从线程池中出来, Tomcat老大给他们讲了上一代旺财和小强的故事, 对他们谆谆教导:“做转账操作的时候一定要小心,别死锁了!”

旺财和小强有点儿愤愤不平:“这我们俩也控制不了啊,这要看程序员写的代码,以及操作系统中的线程调度啊!”

不满归不满,他俩还是有点小期待,想看看可怕的转账代码到底怎么样。

没过多久, 他俩就如愿了:

  1. public static final Object lock = new Object(); 
  2. public void transfer(Account from,Account toint amt){ 
  3.    int fromHash = System.identityHashCode(from); 
  4.    int toHash = System.identityHashCode(to); 
  5.    if(fromHash > toHash){ 
  6.        synchronized(from){ 
  7.            synchronized(to){ 
  8.                from.withdraw(amt); 
  9.                to.deposit(amt); 
  10.            } 
  11.        } 
  12.    } 
  13.    else if(toHash > fromHash){ 
  14.        synchronized(to){ 
  15.            synchronized(from){ 
  16.                from.withdraw(amt); 
  17.                to.deposit(amt); 
  18.            } 
  19.        } 
  20.    } 
  21.    else { 
  22.        synchronized(lock){ 
  23.            synchronized(from){ 
  24.                synchronized(to){ 
  25.                    from.withdraw(amt); 
  26.                    to.deposit(amt); 
  27.                } 
  28.            } 
  29.        } 
  30.    } 

看到这样的代码, 旺财倒吸了一口气,挠着头说:“搞什么鬼,转个账都这么麻烦!”

小强说:“老大不是说了吗,上一代线程老是在转账这里出错,于是代码就重写了。你看,这一次写得就很严谨了,每一次都会去比较两个账户的大小(通过hash code),谁大就先获得谁的锁。 ”

旺财说:“奥,相当于把账户给排了序,假设账户A大于账户B , 那我们俩转账的时候,每次都先获得A的锁,这样就不会互相等待了。 ”

“没错,还有一个特殊情况,如果这两个账户的hash code 相同,那就再去竞争另外一个特殊的锁,谁抢到谁就可以先执行。另一个就在那里等待。”

旺财和小强这次顺利地把转账给执行完了,回去给Tomcat汇报了一遍。

Tomcat老大感慨地说:“有这么复杂的代码,可见使用‘共享内存’的方式来并发编程很不容易啊!”

“共享内存?”

“对啊,你看这些账户的数据,每个线程都可以访问,不就是共享内存吗, 为了能够安全访问,只有来‘加锁’了。 古人说,这个世界上有两种构建软件的方式,一种方法是使其足够简单以至于不存在明显的缺陷,另外一种是使其足够复杂以至于看不出有什么问题。我很担心啊, 现在这个系统就属于第二种,不知道有多少坑在等着我们呢!”

(码农翻身: 实际上这句话是托尼·霍尔说的)

老大不幸言中,终于有一天,这个复杂到看不出问题的系统崩溃了,这个世界又毁灭了。

第三世

第三代的旺财和小强从线程池出来。

出发前,Tomcat老大把前两代线程遇到的问题给他们说了一遍,威胁说:如果再出现死锁,小心你们两个的脑袋!

旺财和小强战战兢兢,如履薄冰地执行代码。

最终他们还是遇到了传说中的可怕的转账代码:

  1. def transfer(from: Account, to: Account,amt:Int){ 
  2.    atomic{ 
  3.        from.withdraw(amt); 
  4.        to.deposit(amt); 
  5.    } 

旺财非常吃惊:“这是什么代码?不是Java?”

小强说:“嗯,不是Java ,是Scala写的,这是运行在JVM上的一个语言。”

(码农翻身注:实际上JVM线程能看到的只是Java 字节码,根本看不到源码,也就不知道是Java写的代码,还是Scala写的代码, 这里只是为了展示方便。)

旺财说:“怎么这么简单,会不会出问题?那个atomic是怎么回事?表示原子执行?”

小强也有点懵,不敢贸然去执行:“咱们还是去问Tomcat老大吧。”

Tomcat看了一眼:“人类程序员又改代码了啊,开始使用Software Transaction Memory(STM)了。 去把STM老头儿叫来,让他给你俩解释。”

STM老头儿满脸沧桑:“放心执行吧,只要你把代码放到atomic中,我就能保证他们像事务一样,实现ACID,哦不,D(持久化)实现不了,这些数据都是在内存中的。”

“这有什么用? ”

“可以让你们俩安全地并发执行啊?”

旺财和小强面面相觑,这连锁都没有,还安全地并发?

“别看没有锁,” STM老头儿说,“在atomic代码开始执行的时候,我会记录下代码块涉及到的数据的值(复制了一份),然后才真正执行,执行完了要‘提交’, 这时候我会看看那些数据的值是否也被别的线程改动了,如果有改动,那本次改动就撤销,重新从代码开始处执行。 ”

老头儿画了一个图,展示旺财从账户A给账户B转20元, 与此同时小强从B向A转30元。

还真是,没有加锁就安全地完成了两个并发操作。

当然,老头儿为了实现这个atomic操作,背后偷偷做了不少事情:复制数据,提交,重复执行。

旺财想起来自己曾经执行过一下Java 的Compare and swap的代码,说道:“你这不就是CAS嘛!”

老头儿说:“原理上类似,都是乐观锁,不过我这个方式和数据库的事务更加类似,所以叫做Software Transaction Memory。”

小强想了想,说道:“不对啊,atomic是个代码块,里边可能有很多代码,涉及到很多class, 你怎么知道哪些字段需要被STM管理起来啊!”

STM老头儿说:“这真是个好问题,实际上,需要程序员们来告诉我。比如使用这个方法”

  1. class Account(val initialBalance : Int){ 
  2.  val balance = Ref(initialBalance) 
  3.  ...... 

“看到那个Ref没有,这就是一种办法,通过它,我就知道这个balance的字段需要让我管理起来,在atomic代码块运行的时候,就需要复制它的值,比较它的值。”

“明白了,但是‘重复执行’有问题啊,假设程序员张大胖是这么写代码的:”

  1. def transfer(from: Account, to: Account,amt:Int){ 
  2.     atomic{ 
  3.         from.withdraw(amt); 
  4.         ...在这里执行一些其他操作,例如打印日志,发送邮件..... 
  5.         to.deposit(amt); 
  6.     } 

“这其中有一些打印日志,发送邮件的操作,那你重复执行,岂不会执行很多次,就完全乱套了。”

STM老头儿说:“不错,想得挺深,你说的这些操作,我把他们叫做副作用,不能重复执行,不能放到atomic代码块中让STM管理。换句话说atomic中的代码应该是幂等的。如果违背了这一点,后果自负!”

小强心中一凛:“这是程序员要操心的事情了,不管我俩的事情, 不过即使如此,他们的代码也极度地简化了,只需要用个atomic,就能实现安全地并发,实在是太爽了。”

旺财说道:“你说得天花乱坠,这STM有什么缺点?”

老头儿说:“天下没有免费的午餐,很容易想到STM的局限性, 如果对于同一个数据,并发写入很多的时候,冲突就大大增加了,不断地重复执行,效率很低。所以更适合写入少,读取多的场景。”

“好吧,我们这就执行这个转账操作,有问题就找你!”

【本文为51CTO专栏作者“刘欣”的原创稿件,转载请通过作者微信公众号coderising获取授权】

戳这里,看该作者更多好文

【编辑推荐】

  1. 2018让程序员崩溃的瞬间!看到哪一个你哭了?
  2. 为什么阿里巴巴要求程序员谨慎修改serialVersionUID 字段的值
  3. 中国有多少个程序员?
  4. 程序员扛过寒冬,一定要看12月的这十篇热门文章
  5. “菜鸟”程序员和“大神”程序员的差别竟然这么大...
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢
工业园 宝城路 江干区 外高桥保税区 大陈各庄
乐福堂乡 天长街道 安乐镇 湖光中街 仁宅村
威尼斯人网站 澳门赌场简介 六合投注网 澳门二十一点游戏平台 网上真钱游戏
PT电子游戏 澳门威尼斯人赌城 轮盘游戏平台 斗牛游戏 真人博彩
庄闲赌场 澳门葡京娱乐网址 澳门威尼斯人官网 葡京娱乐官网 速食轮动
电子游戏 澳门威尼斯人官网 电子游戏 银河平台是真的吗 英皇网址
老虎机定位器 澳门大富豪网址 现金三公注册网址 牛牛游戏下载 现金骰宝 年度十大电子游戏 大小点游戏 玩什么游戏可以挣钱 电子游戏厅 方法奇葩赌博网 巴黎人网站 pt电子游戏哪个最会爆 澳门巴黎人游戏 澳门龙虎斗注册 澳门大富豪网站 押大小排行 真钱打牌 明升网站 十三水技巧 电子游戏下载 二十一点平台 现金网游戏开户平台 澳门百老汇游戏官网 皇博压大小 真钱捕鱼 跑马机游戏 赌博技巧 巴比伦赌场官网 现金三公 地下网址 捕鱼游戏技巧 英皇网站 手机玩游戏赚钱平台 现金网排行 pt电子游戏注册 赌博技巧 电脑玩游戏赚钱平台 海立方游戏 ag电子游戏排行 希尔顿官网 太阳网上压大小 现金赌钱游戏 现金棋牌游戏 真人网站网址 地下开户 九五至尊娱乐网址 澳门梭哈游戏官网 奇葩袖赌博网 鸿胜国际压大小 博狗扑克游戏 德州扑克游戏规则 庄闲代理 奔驰宝马老虎机下载 现金三公开户注册 免费试玩电子游戏 GT压大小 新濠天地注册 现金老虎机网站 纸牌赌博种类 乐天堂开户 澳门永利平台 电脑版捕鱼达人 玩电子游戏入门 斗牛游戏 bbin压大小 网上电子游戏网址 澳门网络下注平台 明升国际网址 明升娱乐 捕鱼达人电子游戏 mg电子游戏试玩 二十一点游戏赌场 澳门万利赌场官网 大小对比网站 现金电子游戏 电子游戏实用技术 老虎机破解器 澳门梭哈官网 澳门百老汇赌场注册 千炮捕鱼兑换现金 网上合法赌场 PT电子游戏 波克棋牌官方下载 天天棋牌 凤凰棋牌 美少女战士电子游戏 什么游戏可以赚人民币 银河国际娱乐 澳门番摊官网 澳门梭哈官网 胜博发电子游戏 电子游戏打鱼机 澳门现金网 大三巴网站 PT电子游戏 澳门银河国际娱乐