拂晓-千云暗组- 努力,执着,意志,精神
  • 饥饿和公平(转)

    2017-04-24

    如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为“饥饿”。而该线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。解决饥饿的方案被称之为“公平性” – 即所有线程均能公平地获得运行机会。

    下面是本文讨论的主题:

    1. Java中导致饥饿的原因:

    • 高优先级线程吞噬所有的低优先级线程的CPU时间。
    • 线程被永久堵塞在一个等待进入同步块的状态。
    • 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法)。

    2. 在Java中实现公平性方案,需要:

    • 使用锁,而不是同步块。
    • 公平锁。
    • 注意性能方面。

    Java中导致饥饿的原因

    在Java中,下面三个常见的原因会导致线程饥饿:

    1. 高优先级线程吞噬所有的低优先级线程的CPU时间。
    2. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
    3. 线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象,因为其他线程总是被持续地获得唤醒。

    高优先级线程吞噬所有的低优先级线程的CPU时间

    你能为每个线程设置独自的线程优先级,优先级越高的线程获得的CPU时间越多,线程优先级值设置在1到10之间,而这些优先级值所表示行为的准确解释则依赖于你的应用运行平台。对大多数应用来说,你最好是不要改变其优先级值。

    线程被永久堵塞在一个等待进入同步块的状态

    Java的同步代码区也是一个导致饥饿的因素。Java的同步代码区对哪个线程允许进入的次序没有任何保障。这就意味着理论上存在一个试图进入该同步区的线程处于被永久堵塞的风险,因为其他线程总是能持续地先于它获得访问,这即是“饥饿”问题,而一个线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。

    线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象

    如果多个线程处在wait()方法执行上,而对其调用notify()不会保证哪一个线程会获得唤醒,任何线程都有可能处于继续等待的状态。因此存在这样一个风险:一个等待线程从来得不到唤醒,因为其他等待线程总是能被获得唤醒。

    在Java中实现公平性

    虽Java不可能实现100%的公平性,我们依然可以通过同步结构在线程间实现公平性的提高。

    首先来学习一段简单的同步态代码:

    1 public class Synchronizer{
    2
    3     public synchronized void doSynchronized(){
    4
    5     //do a lot of work which takes a long time
    6
    7     }
    8 }

    如果有一个以上的线程调用doSynchronized()方法,在第一个获得访问的线程未完成前,其他线程将一直处于阻塞状态,而且在这种多线程被阻塞的场景下,接下来将是哪个线程获得访问是没有保障的。

    使用锁方式替代同步块

    为了提高等待线程的公平性,我们使用锁方式来替代同步块。

    1 public class Synchronizer{
    2     Lock lock = new Lock();
    3     public void doSynchronized() throws InterruptedException{
    4         this.lock.lock();
    5         //critical section, do a lot of work which takes a long time
    6         this.lock.unlock();
    7     }
    8 }

    注意到doSynchronized()不再声明为synchronized,而是用lock.lock()和lock.unlock()来替代。

    下面是用Lock类做的一个实现:

    01 public class Lock{
    02
    03     private boolean isLocked      = false;
    04
    05     private Thread lockingThread = null;
    06
    07     public synchronized void lock() throws InterruptedException{
    08
    09     while(isLocked){
    10
    11         wait();
    12
    13     }
    14
    15     isLocked = true;
    16
    17     lockingThread = Thread.currentThread();
    18
    19 }
    20
    21 public synchronized void unlock(){
    22
    23     if(this.lockingThread != Thread.currentThread()){
    24
    25          throw new IllegalMonitorStateException(
    26
    27               "Calling thread has not locked this lock");
    28
    29          }
    30
    31     isLocked = false;
    32
    33     lockingThread = null;
    34
    35     notify();
    36
    37     }
    38 }

    注意到上面对Lock的实现,如果存在多线程并发访问lock(),这些线程将阻塞在对lock()方法的访问上。另外,如果锁已经锁上(校对注:这里指的是isLocked等于true时),这些线程将阻塞在while(isLocked)循环的wait()调用里面。要记住的是,当线程正在等待进入lock() 时,可以调用wait()释放其锁实例对应的同步锁,使得其他多个线程可以进入lock()方法,并调用wait()方法。

    这回看下doSynchronized(),你会注意到在lock()和unlock()之间的注释:在这两个调用之间的代码将运行很长一段时间。进一步设想,这段代码将长时间运行,和进入lock()并调用wait()来比较的话。这意味着大部分时间用在等待进入锁和进入临界区的过程是用在wait()的等待中,而不是被阻塞在试图进入lock()方法中。

    在早些时候提到过,同步块不会对等待进入的多个线程谁能获得访问做任何保障,同样当调用notify()时,wait()也不会做保障一定能唤醒线程(至于为什么,请看线程通信)。因此这个版本的Lock类和doSynchronized()那个版本就保障公平性而言,没有任何区别。

    但我们能改变这种情况。当前的Lock类版本调用自己的wait()方法,如果每个线程在不同的对象上调用wait(),那么只有一个线程会在该对象上调用wait(),Lock类可以决定哪个对象能对其调用notify(),因此能做到有效的选择唤醒哪个线程。

    公平锁

    下面来讲述将上面Lock类转变为公平锁FairLock。你会注意到新的实现和之前的Lock类中的同步和wait()/notify()稍有不同。

    准确地说如何从之前的Lock类做到公平锁的设计是一个渐进设计的过程,每一步都是在解决上一步的问题而前进的:Nested Monitor Lockout, Slipped Conditions和Missed Signals。这些本身的讨论虽已超出本文的范围,但其中每一步的内容都将会专题进行讨论。重要的是,每一个调用lock()的线程都会进入一个队列,当解锁后,只有队列里的第一个线程被允许锁住Farlock实例,所有其它的线程都将处于等待状态,直到他们处于队列头部。

    01 public class FairLock {
    02     private boolean           isLocked       = false;
    03     private Thread            lockingThread  = null;
    04     private List<QueueObject> waitingThreads =
    05             new ArrayList<QueueObject>();
    06
    07   public void lock() throws InterruptedException{
    08     QueueObject queueObject           = new QueueObject();
    09     boolean     isLockedForThisThread = true;
    10     synchronized(this){
    11         waitingThreads.add(queueObject);
    12     }
    13
    14     while(isLockedForThisThread){
    15       synchronized(this){
    16         isLockedForThisThread =
    17             isLocked || waitingThreads.get(0) != queueObject;
    18         if(!isLockedForThisThread){
    19           isLocked = true;
    20            waitingThreads.remove(queueObject);
    21            lockingThread = Thread.currentThread();
    22            return;
    23          }
    24       }
    25       try{
    26         queueObject.doWait();
    27       }catch(InterruptedException e){
    28         synchronized(this) { waitingThreads.remove(queueObject); }
    29         throw e;
    30       }
    31     }
    32   }
    33
    34   public synchronized void unlock(){
    35     if(this.lockingThread != Thread.currentThread()){
    36       throw new IllegalMonitorStateException(
    37         "Calling thread has not locked this lock");
    38     }
    39     isLocked      = false;
    40     lockingThread = null;
    41     if(waitingThreads.size() > 0){
    42       waitingThreads.get(0).doNotify();
    43     }
    44   }
    45 }

    01 public class QueueObject {
    02
    03     private boolean isNotified = false;
    04
    05     public synchronized void doWait() throws InterruptedException {
    06
    07     while(!isNotified){
    08         this.wait();
    09     }
    10
    11     this.isNotified = false;
    12
    13 }
    14
    15 public synchronized void doNotify() {
    16     this.isNotified = true;
    17     this.notify();
    18 }
    19
    20 public boolean equals(Object o) {
    21     return this == o;
    22 }
    23
    24 }

    首先注意到lock()方法不在声明为synchronized,取而代之的是对必需同步的代码,在synchronized中进行嵌套。

    FairLock新创建了一个QueueObject的实例,并对每个调用lock()的线程进行入队列。调用unlock()的线程将从队列头部获取QueueObject,并对其调用doNotify(),以唤醒在该对象上等待的线程。通过这种方式,在同一时间仅有一个等待线程获得唤醒,而不是所有的等待线程。这也是实现FairLock公平性的核心所在。

    请注意,在同一个同步块中,锁状态依然被检查和设置,以避免出现滑漏条件。

    还需注意到,QueueObject实际是一个semaphore。doWait()和doNotify()方法在QueueObject中保存着信号。这样做以避免一个线程在调用queueObject.doWait()之前被另一个调用unlock()并随之调用queueObject.doNotify()的线程重入,从而导致信号丢失。queueObject.doWait()调用放置在synchronized(this)块之外,以避免被monitor嵌套锁死,所以另外的线程可以解锁,只要当没有线程在lock方法的synchronized(this)块中执行即可。

    最后,注意到queueObject.doWait()在try – catch块中是怎样调用的。在InterruptedException抛出的情况下,线程得以离开lock(),并需让它从队列中移除。

    性能考虑

    如果比较Lock和FairLock类,你会注意到在FairLock类中lock()和unlock()还有更多需要深入的地方。这些额外的代码会导致FairLock的同步机制实现比Lock要稍微慢些。究竟存在多少影响,还依赖于应用在FairLock临界区执行的时长。执行时长越大,FairLock带来的负担影响就越小,当然这也和代码执行的频繁度相关。

     

    Author:admin | Categories:工作间 | Tags:
  • (转)谈谈Redis的SETNX

    2016-05-06

    文章中讲到的问题我在项目中真实遇到过,就是setnx成功了,但expire没成功,导致锁一直存在。所以觉得很有借鉴的意义,就转了过来。
    原文:http://huoding.com/2015/09/14/463
    在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果,不过很多人没有意识到 SETNX 有陷阱!
    阅读更多 »

    Author:admin | Categories:工作间 | Tags:
  • 解决phpredis ‘RedisException’ with message ‘read error on connection’

    2015-08-26

    在跑定时任务时常出现一个Exception如下:
    ‘RedisException’ with message ‘read error on connection’

    是php.ini文件中的一个配置项导致:

    default_socket_timeout = 60
    由于redis扩展也是基于php 的socket方式实现,因此该参数值同样会起作用。

    找到了问题就比较好解决了:

    1、直接修改php.ini,将其设置为我们想要的值(这个不推荐)

    2、在我们的脚本中通过以下方式设置,这样就比较灵活,不对其他脚本产生影响

    ini_set(‘default_socket_timeout’, -1); //不超时

    Author:admin | Categories:工作间 | Tags:
  • mongodb 分片技术(转)

    2015-08-21

    在mongodb里面存在另一种集群,就是分片技术,跟sql server的表分区类似,我们知道当数据量达到T级别的时候,我们的磁盘,内存

    就吃不消了,针对这样的场景我们该如何应对。
    阅读更多 »

    Author:admin | Categories:工作间 | Tags:
  • GIT多个项目使用不同的公钥和私钥

    2012-06-24

    前几天遇到个情况,在github上有多个项目,在公司内部也有两个用Git管理的项目,这几个都要用,而且它们的公钥和私钥还都不一样,肿么办????
    阅读更多 »

    Author:admin | Categories:GIT工作间 | Tags:
  • 帮公司发布招聘Flash程序员

    2011-06-14

    招聘Flash程序员
    地点:北京望京西
    要求:精通flash as3,有两年以上的flash互动项目和全站的开发经验,有良好语言表达能力,有良好的团队合作精神及钻研精神。
    薪资:8K-10K(视能力而定)。
    有意者联系我。QQ:(1185802697)

    Author:admin | Categories:工作间 | Tags:
  • JSFL学习与常用JSFL工具整理

    2011-03-27

    JSFL:Flash JavaScript,见名知义,类似于javascript.用来扩展Flash IDE的功能一个强大工具.学会它会使得你的工作更加有效率。具体相关说明略过,看下面几个例子来理解: 阅读更多 »

  • Proguard.cfg(the file can’t find 系统找不到指定的文件)

    2011-02-01

    好不容易搭好Android开发环境,在用eclipse新建项目的时候出现这个问题,真让人恼火。问题如下图:

    解决办法:
    阅读更多 »

  • 入门3G开发要了解的常识

    2011-01-31

    1.目前主流的智能手机软件平台如下:
    Symbian,Window Mobile,RIM BlackBerry,Android,IPhone,Palm,Brew,Java/J2ME.

    2.3G与2.5G的区别主要在于3G解决了大数据量传输的带宽问题。

    3.Android是基于Linux平台的开源操作系统的名称。

    4.3G = 无线通信+国际互联网 = 移动互联网

    5.符合3G通信标准的技术如下:
    WCDMA,CDMA2000,TD-SCDMA. 联通采用的WCDMA,中国电信采用的是CDMA2000,中国移动采用的是具有自主知识产权的TD-SCDMA。

    Author:admin | Categories:工作间 | Tags:
  • 小雨懒得敲的那些的代码

    2010-12-16

    网页中嵌入flash的代码:
    [html]<div align="center">
    <object id="flashid" classid=
    "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase=
    "http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab
    #version=8,0,0,0" width="100%" height="100%">

    <param name="allowScriptAccess" value="always" />

    <param name="allowFullScreen" value = "true" />

    <!– flash的地址 –>
    <param name="movie" value="index.swf" />

    <!– 允许相同域的脚本访问flash –>
    <param name="allowScriptAccess" value="sameDomain" />

    <param name="quality" value="high" />

    <!– 设置背景色 –>
    <param name="bgcolor" value="#CC0000" />

    <!– 设置flash透明–>
    <!–<param name="wmode" value="transparent"> –>

    <!– 传参数的地方 –>
    <param name="FlashVars" value="" />

    <!– src:flash的地址; bgcolor:背景色;wmode:可以设置flash透明,需要
    有背景色时去掉此属性;时width:宽度; height:高度–>
    <embed src="index.swf" name="flashname" allowScriptAccess=
    "sameDomain" allowFullScreen="true" FlashVars="" quality="high"
    bgcolor="#CC0000" wmode="transparent" width="100%" height="100%"
    align="middle"
    allowScriptAccess="always" type="application/x-shockwave-flash"
    pluginspage="http://www.macromedia.com/go/getflashplayer" />
    </object>
    </div>[/html]
    阅读更多 »

    Author:admin | Categories:工作间 | Tags: