4
4
< html xmlns ="http://www.w3.org/1999/xhtml " lang ="en " xml:lang ="en ">
5
5
< head >
6
6
< title > 资源争用模型(泛多线程编程)</ title >
7
- <!-- 2017-09-24 日 14:34 -->
7
+ <!-- 2017-09-25 一 22:33 -->
8
8
< meta http-equiv ="Content-Type " content ="text/html;charset=utf-8 " />
9
9
< meta name ="generator " content ="Org-mode " />
10
10
< meta name ="author " content ="王月阳 " />
@@ -184,11 +184,10 @@ <h2>Table of Contents</h2>
184
184
</ li >
185
185
< li > < a href ="#sec-5 "> 5. Java内存模型与资源争用</ a >
186
186
< ul >
187
- < li > < a href ="#sec-5-1 "> 5.1. Java内存模型与运行时内存分布, 分析被争用的资源</ a > </ li >
187
+ < li > < a href ="#sec-5-1 "> 5.1. 分析被争用的资源</ a > </ li >
188
188
< li > < a href ="#sec-5-2 "> 5.2. Java如何使用不共享内存模型解决线程安全问题</ a >
189
189
< ul >
190
- < li > < a href ="#sec-5-2-1 "> 5.2.1. copyOnWriteList</ a > </ li >
191
- < li > < a href ="#sec-5-2-2 "> 5.2.2. ThreadLocal</ a > </ li >
190
+ < li > < a href ="#sec-5-2-1 "> 5.2.1. ThreadLocal</ a > </ li >
192
191
</ ul >
193
192
</ li >
194
193
< li > < a href ="#sec-5-3 "> 5.3. Java如果使用不可变资源模型解决线程安全问题</ a >
@@ -489,7 +488,7 @@ <h2 id="sec-5"><span class="section-number-2">5</span> Java内存模型与资源
489
488
</ p >
490
489
</ div >
491
490
< div id ="outline-container-sec-5-1 " class ="outline-3 ">
492
- < h3 id ="sec-5-1 "> < span class ="section-number-3 "> 5.1</ span > Java内存模型与运行时内存分布, 分析被争用的资源</ h3 >
491
+ < h3 id ="sec-5-1 "> < span class ="section-number-3 "> 5.1</ span > 分析被争用的资源</ h3 >
493
492
< div class ="outline-text-3 " id ="text-5-1 ">
494
493
< p >
495
494
Java的内存模型主要包含两部分,一部分是主内存,也就是所有java线程共享;另一部分是java线程的本地变量,包含局部变量和共享
@@ -502,23 +501,92 @@ <h3 id="sec-5-1"><span class="section-number-3">5.1</span> Java内存模型与
502
501
doServie方法。所以,servlet对象的属性在web编程过程中是被当作共享资源争用的,而doService方法里定义的局部变量是每个线程
503
502
私有的。所以,servlet的属性是非线程安全的,需要一定的机制去保证线程安全。
504
503
</ p >
504
+
505
+ < p >
506
+ 业务系统中,我们怎么分析被争用的资源呢?其实是一样的,关键是分析哪些资源是共享的。比如电商系统中账户的金额,比如商品
507
+ 的数量。这些都是会被不同业务线程争用的资源。下面我们具体分析一下,怎么利用资源争用模型保证数据安全的。
508
+ </ p >
509
+
510
+ < p >
511
+ < b > *</ b > 账户金额问题
512
+ 在一个电商系统中,账户是会存在同一个时刻有多个操作请求的,比如用户购买商品发生账户扣款的同时也在充值,比如用户同时从
513
+ 多个不同终端进行购买支付操作,比如用户连续支付多笔订单。在这一系列的业务场景中,我们怎么通过资源争用模型来保证账户金
514
+ 额的正确呢?
515
+ </ p >
516
+
517
+ < p >
518
+ 首先我们分析账户金额为啥会不正确?假设账户原始金额为100.00元,业务线程A要为账户充值10元,业务线程B要从账户里扣除30元。
519
+ 业务线程A先查询到账户里有100元,然后中内存中将10元加上,此时内存中的账户是110元,然后发生了线程上下文切换,线程A被挂
520
+ 起,线程B执行,B也从数据库中读到了账户余额是100元,然后将支付的30元扣除,此时B认为账户余额应该是70元,写入到数据库中。
521
+ 线程B执行完成,用户正常支付扣款。然后线程A切换执行,又把账户的余额设置成了110元。线程A也正常执行完成,用户充值成功。
522
+ 可是,此时账户里是110元,但正常情况下,账户里应有100-30+10 = 80元。那么问题出在哪里呢?
523
+ </ p >
524
+
525
+ < p >
526
+ 可以说是线程A不知道线程B已经把账户里的钱改动过了,所以认为它持有的金额是正确的。也可以说是A的操作被B打断了,所以导致
527
+ 了A的数据不正确。从资源争用的角度看怎么解决问题呢?排队!让线程A和B的操作依次进行,这样就保证数据安全了。怎么排队呢,
528
+ 用一个状态值表明账户是否被占用,占用的时候是不允许其他线程操作的。这就相当于用一个互斥量来抽象资源状态,空闲状态的资
529
+ 源才能被操作,否则线程就要等待。这是不是就是我们通常理解的锁呀。如果是单机,我们可以用java的锁来实现这个互斥量;如果
530
+ 是分布式系统,我们可以使用redis或者zookeeper提供的原子操作实现的分布式锁来抽象资源状态,进而实现对不同线程的互斥操作。
531
+ </ p >
532
+
533
+ < p >
534
+ 另一种排队方案是什么呢?上面的方案是通过锁实现的排队,我们还可以认为的让所有的操作排队,比如依次只处理一个对账户的写
535
+ 操作。怎么实现呢?让所有对同一个账户的操作都放到一个队列里面,只有一个消费者线程从队列里取操作去处理账户金额,这样也
536
+ 实现了对同一个账户操作的排队。java里常用的锁,比如有synchronized关键字,JUC的Lock实现等。
537
+ </ p >
538
+
539
+ < p >
540
+ 还有一种实现方式是什么呢?就是利用数据库提供的行级锁,为每行账户记录加一个版本号,业务线程操作账户金额时,必须带着之
541
+ 前查出来的版本号,那线程内部版本号和数据库存的版本号进行对比,相同的才能更新账户数据,否则就失败。这也实现了对账户操
542
+ 作的排队。这种方式其实就是不保留历史记录的MVCC方式。
543
+ </ p >
544
+
545
+ < p >
546
+ 以上三种方式都通过自己的方式实现里对账户操作的排队,保证了不同业务线程对共享的账户金额的安全操作。前面提到的商品数量
547
+ 问题也可以通过这三种排队方式去实现数据安全。我们可以大胆的推理,所有的共享资源,都可以通过这三种排队方式实现安全。
548
+ </ p >
549
+
550
+ < p >
551
+ 举的这个例子都是写操作,其实还有独立的读操作,比如商品数量问题,不同用户浏览商品的时候,都是对商品数量的读操作,这种
552
+ 读操作其实是不互斥的,只有发生写操作的时候,才需要互斥。也就是说资源状态中,被占用又可以分为两种状态,被写占用还是读
553
+ 占用,写占用的时候其他任何读写线程都不能操作,读占用的时候其他读线程可以操作,但写线程会被阻塞。java里的读写锁,就实
554
+ 现了对这种场景的抽象。读写锁抽象里共享资源的三个状态,实现里对共享资源操作的排队,保证里共享资源的安全。
555
+ </ p >
505
556
</ div >
506
557
</ div >
507
558
< div id ="outline-container-sec-5-2 " class ="outline-3 ">
508
559
< h3 id ="sec-5-2 "> < span class ="section-number-3 "> 5.2</ span > Java如何使用不共享内存模型解决线程安全问题</ h3 >
509
560
< div class ="outline-text-3 " id ="text-5-2 ">
510
- </ div > < div id ="outline-container-sec-5-2-1 " class ="outline-4 ">
511
- < h4 id ="sec-5-2-1 "> < span class ="section-number-4 "> 5.2.1</ span > copyOnWriteList</ h4 >
561
+ < p >
562
+ 除去上面的排队模型,还有什么方式可以解决争用呢?可以让业务线程不争用啊,大家不共享了,自然就不争用了。比如java中常见
563
+ 的SimpleDateFormat对象如果给多个线程同时使用就会出现格式化的日期不对的情况。为啥不对呢,因为SDF对象的属性被多个线程争
564
+ 用,导致了多个线程使用的时候资源状态混乱,数据错乱。从不共享的角度怎么解决问题呢?那就为每一个线程分配一个SDF对象,这
565
+ 样线程之间就不会争用一个SDF对象了,没有了争用,资源就安全了,代码执行就正常了。java中常用的实现方式有两种,一种是每次
566
+ 使用的时候new一个SDF对象出来,另一种方式是利用java提供的ThreadLocal对象。
567
+ </ p >
568
+ </ div >
569
+ < div id ="outline-container-sec-5-2-1 " class ="outline-4 ">
570
+ < h4 id ="sec-5-2-1 "> < span class ="section-number-4 "> 5.2.1</ span > ThreadLocal</ h4 >
571
+ < div class ="outline-text-4 " id ="text-5-2-1 ">
572
+ < p >
573
+ ThreadLocal在java中叫做线程封闭。什么意思呢,就是把对象封闭到使用的线程中,不给其他线程用,进而不发生争用,保证线程
574
+ 安全。怎么实现的呢,大概的模型就是利用一个map对象,key是线程,值是业务对象。每次是通过线程当key获取value的,自然不会
575
+ 重复。
576
+ </ p >
512
577
</ div >
513
- < div id ="outline-container-sec-5-2-2 " class ="outline-4 ">
514
- < h4 id ="sec-5-2-2 "> < span class ="section-number-4 "> 5.2.2</ span > ThreadLocal</ h4 >
515
578
</ div >
516
579
</ div >
517
-
518
580
< div id ="outline-container-sec-5-3 " class ="outline-3 ">
519
581
< h3 id ="sec-5-3 "> < span class ="section-number-3 "> 5.3</ span > Java如果使用不可变资源模型解决线程安全问题</ h3 >
520
582
< div class ="outline-text-3 " id ="text-5-3 ">
521
- </ div > < div id ="outline-container-sec-5-3-1 " class ="outline-4 ">
583
+ < p >
584
+ 不可变模型相当于是不争用的一个特例。不可变意味着可以任意复制多份到任意线程中,不会争用。可是如果要修改对象的数据怎么
585
+ 办呢?这个时候就要通过copyOnWrite实现了。copyOnWrite不会修改原对象的数据,而是会原子的复制原对象的数据, 并把新的数据
586
+ 写到新对象里面,然后返回一个新对象。java通过final关键字实现不可变对象。
587
+ </ p >
588
+ </ div >
589
+ < div id ="outline-container-sec-5-3-1 " class ="outline-4 ">
522
590
< h4 id ="sec-5-3-1 "> < span class ="section-number-4 "> 5.3.1</ span > Final关键字</ h4 >
523
591
</ div >
524
592
< div id ="outline-container-sec-5-3-2 " class ="outline-4 ">
@@ -591,7 +659,7 @@ <h3 id="sec-7-2"><span class="section-number-3">7.2</span> Zookeeper一主多从
591
659
</ div >
592
660
< div id ="postamble " class ="status ">
593
661
< p class ="author "> Author: 王月阳</ p >
594
- < p class ="date "> Created: 2017-09-24 日 14:34 </ p >
662
+ < p class ="date "> Created: 2017-09-25 一 22:33 </ p >
595
663
< p class ="creator "> < a href ="http://www.gnu.org/software/emacs/ "> Emacs</ a > 24.5.1 (< a href ="http://orgmode.org "> Org</ a > mode 8.2.10)</ p >
596
664
< p class ="validation "> < a href ="http://validator.w3.org/check?uri=referer "> Validate</ a > </ p >
597
665
</ div >
0 commit comments