Java Threads邮件列表存档

Java线程的CSP模型

发件人:P.H.韦尔奇@ukc.ac.uk
日期:1999年3月26日星期五17:33:18 GMT

对于所有周五深夜工作的人。。。JAVA多线程的CSP模型===================================下面是Java的sync/wait/notify原语的CSP模型草案。这并不像我担心的那么糟糕!这是应该的。Java原语不难非正式地解释。。。所以同样应该任何正式的解释都是真实的。如能提供反馈,我将不胜感激这张草稿有什么问题。我们从结局开始。。。结论:============假设这个模型成立,它为我们提供了Java的形式化语义多线程。尽管遇到了所有问题并发出了警告关于这个主题,我在Java社区中很少看到对没有这样的东西。但如果没有它,我们将始终感到不舒服关于使用多个控制线程的任何产品的安全性。这里给出的特定语义是一个*CSP*语义。The importance of the*CSP*是一个并发系统的代数——一个形式化的我们可以精确指定需求的数学(包括比如死锁自由)并证明我们的实现满足他们。此外,一些强大且成熟的CSP工具(例如,来自Formal的FDR可以应用Systems Ltd.和Jeremy Martin的死锁/卫星检查器)。这意味着任何多线程Java代码——不仅仅是使用直接访问CSP原语的JCSP或CTJ库成为易于进行正式和(部分)自动化分析。如果对此不感兴趣Java社区,那么我们都会遇到很多麻烦。Java设计人员、实现人员、测试人员和维护人员都满身伤痕它的多线程原语,但应用程序强制使用它们。未来,如果还没有,那么财务关键型和安全关键型应用程序将多线程。多线程的CSP模型将此应用于实体工程基础。没有人可以忽视这一点。任何这样做的人至少都会,如果追查到一起乱七八糟的事故,将面临诉讼风险由于某些竞争条件导致的系统故障:“所以,比尔,你在销售系统时并不知道它是不是无死锁。。。你甚至从未尝试过这些标准程序可能已经发现了?!!尽管有免责声明,你会吗说这表明对你的客户?"例如,现在应该可以构建一个CSP模型错误的ALT算法:http://www.hensa.ac.uk/parallel/groups/wotug/java/discussion/4.html连同压力测试(如“周末”帖子所述)一起应用Jeremy的死锁检查器,并观察它快速弹出一条可暴露的跟踪僵局。哇——我真希望几年前我们有这样的机会!当然,对于“校正的”ALT算法,我们最好重复这一点看看有没有什么恶心的东西冒出来!假设没有;-),我们可以搬家证明这是ALT的正确实施。大多数使用sync/wait/notify原语的Java多线程代码--包括我自己的代码——看起来很可疑。正如托尼·霍尔(Tony Hoare)在1980年所说图灵奖演讲中,销售两种计算机系统:o那些明显正确的。。。o和那些没有明显错误的。。。他还指出,当然,生产后者要容易得多。猜猜我们在兜售哪种!我想知道会有多少惊喜出现当我们开始将CSP工具应用于Java代码时?语法:=======这个模型是用霍尔书中的CSP版本构建的——所以它是恐怕有点过时了。另外,我不知道CSP的正确ASCII语法,所以我自己编。下标:“A-subscript-i”将以数组表示法写入“A[i]”复制的运算符:执行对某个集合进行索引的外部选择(例如)-例如。[]事件[i]-->PX中的i将写在一行中:[](X中的事件[i]-->P|i)字母表:进程“P”感兴趣的事件集是“alpha(P)”对象和线程:====================让“Objects”是所有Java对象的编号。对于CSP建模的任何特定Java应用程序,可以是仅限于应用程序中任何线程所在的对象始终“同步”。通常,这将是有限的和小的。让“线程”是所有Java线程的编号。对于CSP建模的任何特定Java应用程序,可以是仅限于创建和启动的线程。有时,这可能是无限的或很大的。同步:=============让“声明”和“发布”成为事件集:claim={claim[o][t]|o在Objects中,t在Threads}中release={release[o][t]|o在Objects中,t在Threads}中设“LOCK”为一组进程:LOCK={对象中的LOCK[o]|o}每个“LOCK[o]”进程只是一个二进制信号量——即。alpha(LOCK[o])=线程}联合中的{claim[o][t]|t{线程中的释放[o][t]|t}LOCK[o]=[](在线程中声明[o][t]->释放[o][t]->LOCK[o]|t)因此,一旦线程声明“LOCK[o]”同一个线程将释放它“LOCK[o]”将被拒绝。Java“synchronized”块:同步(o){P(P)}建模依据:(权利要求[o][me]-->P);(释放[o][me]-->跳过)其中“me”是执行此代码的线程的索引。的主体块“P”是(递归建模为)一个进程。请注意,当存在竞争过程时,没有定义哪个过程拿到锁。“锁定[o]”过程没有承诺如何竞争对索赔人进行管理(例如,采用先进先出法)。这是定义的(或者更确切地说,未定义)。这很容易!实际上,这太容易了:-(。。。Java允许锁拥有线程“自动”重新声明/释放锁(例如,当“synchronized”方法在同一对象)。在“LOCK[o]”中对此类索赔/释放堆栈进行建模由于必须处理一个锁线程,这个过程会很复杂通过“等待”让它离开——因为它必须重新认领锁(如果和何时在相同的堆栈级别和任意数量的线程上不同的堆栈级别,可能会在过渡期间将其锁定)。因此,让我们不要使用“LOCK[o]”,而是在任何合理的实现都是如此。每个线程(索引“me”)为任何对象维护一个计数“count[o][me]”,“o”,在其上可以“同步”。每个“count[o][me]”都已初始化至零。然后,免除occam-style语法,Java“synchronized”块:同步(o){P(P)}建模依据:国际单项体育联合会计数[o][me]=0(索赔[o][me]->P');(释放[o][me]-->跳过)count[o][me]>0P’其中,对“++”/“--”深表歉意(但它只适合一行):P'=计数[o][me]++;P;计数[o][me]--也就是说,线程不需要在任何索赔/发布事件上进行同步如果它已经有锁了!等待并通知:================让“wait-a”、“wait-b”和“notify”成为事件集:wait-a={wait-a[o][t]|o在Objects中,t在Threads}中wait-b={wait-b[o][t]|o在Objects中,t在Threads}中notify={notify[o][t]|o在Objects中,t在Threads}中让“等待”成为一组过程:WAIT={对象中的WAIT[o]|o}哪里:线程}联合中的alpha(WAIT[o])={WAIT-a[o][t]|t线程}联合中的{wait-b[o][t]|t{在线程中通知[o][t]|t}WAIT[o]=等待[o][0]哪里:α(WAIT[o][n])=α(WAIT[o]),对于所有n>=0WAIT[o][0]=([](线程中的WAIT-a[o][t]-->WAIT[o][1]|t))[]([](通知线程中的[o][t]-->WAIT[o][0]|t)其中,对于n>0:WAIT[o][n]=([](线程中的WAIT-a[o][t]-->WAIT[o][n+1]|t))[]([](通知[o][t]-->[](线程中的wait-b[o][s]-->wait[o][n-1]|s)|线程中的t))因此,每个对象“o”都有一个“WAIT[o]”过程。最初,这有计数字段(由“WAIT”的第二个索引表示)设置为零。这个count保存正在上执行Java“wait()”的线程数对象“o”。Java代码:o.wait();建模依据:释放[o][me]-->wait-a[o]][me]-->wait-b[o][我]-->claim[o][Cme]-->跳过其中“me”是执行此代码的线程的索引。请注意,这些操作的发生与“count[o]”的值无关,并且不更改它。Java中的约束之一是“o.wait”(或“o.notify”)只能由当前持有“o”监视器锁的线程调用,即。从“synchronized(o)”块内部。如果线程违反了此约束,上述与“LOCK[o]”并行运行的“o.wait()”模型表示调用线程将被永久阻塞(即死锁)。这似乎是一种合适的惩罚(并且应该可以通过CSP分析工具)。此序列中的第一个“release[o][me]”释放“LOCK[o]”信号量。假设应用程序没有违反上述约束,这将肯定会发生,因为“LOCK[o]”将等待“release[o][me]”(只有这个线程会发送)。接下来,这个序列中的“wait-a[o][me]”总是可以被等待“WAIT[o][n]”进程并增加其内部计数。请注意,不能保证“WAIT[o][n]”过程*将*永远接受“等一等我”。就像在Java中一样,不能保证一个等待的线程会被注意到,然后被释放。假设“wait-a[o][me]”发生了,线程现在阻塞在“wait-b[o][me]”事件。直到“WAIT[o][n]”进程接收到“notify[o][t]”。请参见下文。最后,这条线必须与所有竞争对手竞争,“宣称自己”回收“LOCK[o]”。Java代码:o.notify();建模依据:通知[o][me]-->跳过其中“me”是执行此代码的线程的索引。请注意不涉及释放并随后重新声明“LOCK[o]”,这是根据Java(到目前为止尚未规范化)的规则。目前,该模型确实允许调用“o.notify()”,而无需保留“LOCK[o]”——但这将在下一节中处理。对于“WAIT[o][n]”流程,此“通知我”始终是可以接受的。如果“n”为零,则没有线程等待“o”和通知被接受但被忽略。这是Java所要求的。如果“n”大于零,则至少有一个线程在“o”上等待。任何等待“o”的线程“s”将尝试在“wait-b[o][s]”上同步。因此,当“WAIT[o][n]”接受“notify[o]][t]”时,它将接受其中一个“wait-b[o][s]”事件。如果提供多个,则为任意选择制造完成。这符合Java的(非正式)规则——没有承诺在收到通知后,公平地释放等待线程。接受后其中一个“wait-b[o][s]”事件是“wait[o]][n]”,它会减少计数。幸运线程“s”刚刚从其“wait-b[o][s]”中释放出来,现在必须重新获取“o”上的锁——请参阅中最后的“claim[o][me]”“o.wait()”模型。没有获得此项的特殊特权锁定。。。它必须与所有其他试图声称它的线程竞争……因此出现了“什么,没有鸡?”的困难!这种可悲的行为是此CSP正确捕获。一个重要的观察结果是,“WAIT[o][n]”永远不会停留在等待中对于“wait-b[o][s]”——至少有一个始终可用。因此,我们可以保证,对于“n”的*某些*值,“WAIT[o][n]”将始终能够服务任何“wait-a[o][t]”或“notify[o][t]”事件。这是什么是上面两次使用的短语“总是可以接受的”(和以下)。上述模型并没有强制要求“notify[o][me]”总是接受——只是它总是可以接受的。一个Java线程正在执行“o.notify()”可能不应该无限期地阻塞,但应该始终阻塞释放等待线程后终止(如果存在)。然而,必须进行一些同步,因为数据结构是共享的必须检查并更新“o.wait()”方法。可能是这样同步是“公平的”,并保证最终为“o.notify()”。。。但我还没有看到(非正式)Java规范?鉴于未就进入做出任何承诺从“等待”到“同步”块或最终释放,这样的承诺将不一致。如果出现特殊版本的Java(例如用于实时应用程序)*承诺最终进入“synchronized”块并发布从“o.wait()”方法开始,我们必须用扩展的CSP对它们建模它抓住了“最终”或“公平”为活动提供服务的概念。阿德里安·劳伦斯的CSPP解决了优先权的概念能够做到这一点。监控同步和等待/通知规则:======================================上述CSP模型假设的Java规则是“o.wait()”和“o.notify()”只能由当前持有锁定对象“o”。该规则由Java运行时系统执行。违反此规则的Java应用程序可能会编译,但会出现异常在运行时抛出。要签出不违反此规则的Java应用程序上述模型就足够了。然而,如果我们担心在CSP中建模很简单。如前所述,“o.wait()”规则已经由当前模型。要获得“o.notify()”的规则,最简单的方法是是修改“LOCK[o]”过程,以包括所有“notify[o][t]”事件。然后,它可以拒绝来自具有没有声称。。。并接受来自具有以下功能的线程的“通知”:alpha(LOCK[o])=线程}联合中的{claim[o][t]|t线程}联合中的{release[o][t]|t{在线程中通知[o][t]|t}LOCK[o]=[](在线程中声明[o][t]-->LOCKED[o][t]|t)哪里:阿尔法(锁定[o][t])=阿尔法(锁定[o])LOCKED[o][t]=(释放[o][t]-->LOCK[o])[](通知[o][C]-->LOCKED[o][t])通知所有人:===========要完成Java线程同步模型,我们需要捕获“o.notifyAll()”。这意味着释放所有线程正在等待“o”,即已执行“o.wait()”。如果没有线程正在等待,这很好,调用不会导致任何结果。这很容易:定义一组“notifyAll[o][t]”方法,添加相关的将“LOCK[o]”和“WAIT[o]变化。“LOCK[o]”扩展防止线程执行时出现“notifyAll[o][t]”“t”没有要求。“WAIT[o]”扩展目前发布了所有等待线程——没有新线程能够执行“o.wait()”直到完成。完整的模型将在下一节完整地介绍。模型概要:=====================集合(可Ennumerable):-------------------对象,线程活动:-------claim={claim[o][t]|o在Objects中,t在Threads}中release={release[o][t]|o在Objects中,t在Threads}中wait-a={wait-a[o][t]|o在Objects中,t在Threads}中wait-b={wait-b[o][t]|o在Objects中,t在Threads}中notify={notify[o][t]|o在Objects中,t在Threads}中notifyAll={notifyAll[o][t]|o在Objects中,t在Threads}中`JVM的进程:----------------每个Object有两个过程:LOCK={对象中的LOCK[o]|o}WAIT={对象中的WAIT[o]|o}哪里:alpha(LOCK[o])={线程中的声明[o][t]|t}并集线程}联合中的{release[o][t]|t线程}联合中的{notify[o][t]|t{线程中的notifyAll[o][t]|t}LOCK[o]=[](在线程中声明[o][t]-->LOCKED[o][t]|t)其中:α(锁定[o][t])=α(锁定[o])锁定[o][t]=(释放[o][t]-->锁定[o])][](通知[o][t]-->锁定[o][t])[](通知所有[o][t]-->锁定[o][t])也:alpha(WAIT[o])={线程中的WAIT-a[o][t]|t}并集线程}联合中的{wait-b[o][t]|t线程}联合中的{notify[o][t]|t{线程中的notifyAll[o][t]|t}WAIT[o]=等待[o][0]哪里:α(WAIT[o][n])=α(WAIT[o]),对于所有n>=0WAIT[o][0]=([](线程中的WAIT-a[o][t]-->WAIT[o][1]|t))[]([](通知线程中的[o][t]-->WAIT[o][0]|t)[]([](notifyAll[o][t]-->WAIT[o][0]|t在线程中)其中,对于n>0:WAIT[o][n]=([](线程中的WAIT-a[o][t]-->WAIT[o][n+1]|t))[]([](通知[o][t]-->[](线程中的wait-b[o][s]-->wait[o][n-1]|s)|螺纹中的t))[]([](notifyAll[o][t]-->RELEASE[o][n]|t在线程中)其中:α(释放[o][n])=α(等待[o]),对于所有n>=0释放[0][0]=等待[o][0]其中,对于n>0:RELEASE[o][n]=[](线程中的等待-b[o][2s]-->RELEASE[o][1]|s)计数(可以是进程):----------------------------count={count[o][t]|o在Objects中,t在Threads}中用户进程:---------------应用程序/小程序/whateveret中的每个启动线程都是一个进程。这些过程形成了ennumerated集合:USER={Users}中的USER[me]|me其中Users是(所有Java)Threads的子集。USER[me]的字母表为:alpha(USER[me])=Objects}联合中的{claim[o][me]|o对象}联合中的{release[o][me]|o对象}联合中的{wait-a[o][me]|o对象}联合中的{wait-b[o][me]|o{通知对象中的[o][me]|o}联合{对象中的notifyAll[o][me]|o}对于每个“o”,每个“USER[me]”必须将“count[o][me”初始化为零在对象中。Java“synchronized”块:同步(o){P(P)}是“用户[我]”生活的一部分,对于某些“我”来说,由以下内容建模:国际单项体育联合会计数[o][me]=0(索赔[o][me]-->P');(释放[o][me]-->跳过)count[o][me]>0P’哪里:P'=计数[o][me]++;P;计数[o][me]--Java代码:o.wait();建模依据:释放[o][me]-->wait-a[o]][me]-->wait-b[o][我]-->claim[o][Cme]-->跳过Java代码:o.notify();建模依据:通知[o][me]-->跳过Java代码:o.notifyAll();建模依据:notifyAll[o][me]-->跳过一个完整的系统:------------------一个完整的Java多线程系统就是所有“JVM”和用户进程:(||(在对象中等待[o]|o))||(||(对象中的LOCK[o]|o)||(||(用户中的USER[t]|t)结论:============正面看!彼得·韦尔奇。(1999年3月26日)


上次更新时间:1999年11月2日星期二12:11:43
维护人皮特·韦尔奇.