众所周知,Java平台上的多线程编程是一项艰巨的任务。 实际上,一般的理论似乎是,多线程编程最好留给Java专家处理。 Sun Microsystems通过将以下内容声明为EJB体系结构的目标之一(在EJB规范中,请参阅参考资料 )间接地扩展了该概念:

应用程序开发人员将不必了解低级事务和状态管理详细信息,多线程,连接池或其他复杂的低级API。

以此为出发点,许多Java开发人员回避设计和开发多线程应用程序也就不足为奇了。 但是事实是,很多(即使不是大多数)企业问题也最适合通过某种形式的多线程解决,而EJB和类似框架并不总是被认为是简单的答案。

在这篇由三部分组成的文章中,我向您介绍了一种理论,该理论尊重并发编程的复杂性,而又不会对您隐瞒或使学习和应用不必要的困难。 通信顺序过程(CSP)是一种精确的并发性数学理论,可用于构建多线程应用程序,这些应用程序可确保没有并发性的常见问题,并且(可能更重要的) 事实证明是这样的。

在向您介绍CSP的理论及其基于Java语言的实现(JCSP库)之前,我想确保我们有一个共同的讨论框架。 我从Java平台上的并发编程的技术概述开始,然后是多线程应用程序开发的陷阱的深入概述。 即种族危险,僵局,活锁和资源匮乏。 最后,我将详细讨论为什么您无法验证您的多线程Java应用程序,并确认现有变通办法的最终目的。

不要错过本系列的其余部分!

“面向Java程序员的CSP”是对通信顺序过程(CSP)的三部分介绍,该过程是并发编程的范式,它遵循了复杂性而又不放弃它。 阅读本系列的其他部分:

第2部分:使用JCSP并发编程

第3部分:JCSP中的高级主题

掌握了这些基础知识之后,您应该充分理解JCSP的优点,它是我在第2部分中讨论的Java平台上多线程编程问题的概念性和实用性解决方案,以及Java上CSP的更高级应用程序平台,我将在第3部分中进行讨论。

请注意,为方便起见,本文的所有三个部分都会同时发布。 本文假定您通常熟悉Java语言中的并发编程,尽管在此我确实提供了该主题的简要概述。 有关更多详细信息,请参阅“ 相关主题”部分。

Java语言并发编程

就其本身而言,并发编程是一种可同时在单个系统上或跨多个系统进行操作执行的技术。 这些操作实质上是指令序列,例如单个顶级任务的子任务,可以作为线程或进程并行执行。 线程和进程之间的本质区别在于,虽然进程通常是独立的(例如,单独的地址空间),因此只能通过系统提供的进程间通信机制进行交互,但是线程通常共享单个进程的状态信息,并且可以共享对象。内存和系统资源直接。

您可以通过以下两种方法之一通过多个过程来实现并发。 第一种方法是在同一个处理器上运行进程,而OS处理它们之间的上下文切换。 (可以理解,这种切换比在同一进程中的多个线程之间的上下文切换要慢。)第二种方法是通过在不同的物理处理器上运行这些多个进程来构建大规模并行且复杂的分布式系统。

在内置支持方面,Java语言提供了通过线程进行并发编程的功能。 每个JVM可以一次支持多个执行线程。 您可以通过以下两种方式之一用Java语言创建线程:

  • 通过子类化java.lang.Thread 。 在这种情况下,子类的重写run()方法必须包含实现线程的运行时行为的代码。 您可以通过实例化子类对象然后在其上调用start()方法来执行此代码,从而在内部执行run()方法。
  • 通过创建Runnable接口的自定义实现 。 此接口包含一个名为run()方法,您可以在其中放置应用程序代码。 您可以通过实例化Implementor类的对象来执行此代码,然后在创建新Thread时将其作为构造函数参数传入。 然后,您可以调用新创建的线程对象的start()方法来开始执行新的控制线程。

线程安全和同步

如果Java对象中的方法可以在多线程环境中安全运行,则称该方法是线程安全的 。 为了实现此安全性,必须存在一种机制,通过该机制,多个运行相同方法的线程可以同步其操作,以便在访问同一对象或代码行时仅允许其中一个继续执行。 这种同步要求线程使用称为信号量的对象相互通信。

一种特定类型的信号量称为互斥信号量或互斥 量 。 顾名思义,此信号量对象的所有权是互斥的,因为在任何给定时间只有一个线程可以拥有互斥量。 任何其他尝试获取所有权的线程都将被阻止,并且必须等待,直到拥有该线程的线程释放互斥量。 如果有多个线程在排队等待同一个互斥锁,则当当前所有者释放该互斥锁时,只有一个线程可以获取该互斥锁; 其他人将继续封锁。

在1970年代初期,CAR Hoare和其他人开发了一个称为监视器的概念(请参阅参考资料 )。 监视器是代码的主体,其访问由互斥量保护。 任何希望执行此代码的线程必须在代码块的顶部获取关联的互斥体,并在底部释放它。 因为在给定的时间只有一个线程可以拥有互斥锁,所以这可以有效地确保仅欠线程可以执行监视代码块。 (受保护的代码不必是连续的,例如,Java语言中的每个对象都具有与其关联的单个监视器。)

任何使用Java语言进行线程编程的开发人员都将立即将以上内容视为synchronized关键字的作用。 保证在synchronized块内包含的Java代码可以在任何给定时间由单个线程运行。 在内部,通过运行时将synchronized关键字转换为一种情况,其中所有竞争线程都在尝试获取与其(线程)在其上运行的对象实例相关联的(单个)互斥锁。 成功获取互斥锁的线程将运行代码,并在退出synchronized块时释放互斥锁。

等待和通知

wait / notify的构造在Java语言的线程间通信机制中也起着重要作用。 基本思想是,一个线程需要可以由另一线程实现的特定条件。 因此,它等待条件被满足。 条件为真后,引起线程通知等待线程唤醒并从中断的位置继续进行。

synchronized机制相比, wait / notify机制更加难以理解和推理。 要推理使用wait / notify的一种方法的行为逻辑,就需要您推理使用该方法的所有方法的逻辑。 一次推理一种方法,将其与其他方法隔离开来,是一种得出关于整个系统行为的错误结论的可靠方法。 显然,随着要推理的方法数量的增加,这样做的复杂性会Swift增加。

线程状态

我之前提到过,必须调用新创建的线程的start()方法来开始执行。 但是,仅调用start()方法并不一定意味着线程立即开始运行。 此方法只是将线程的状态从new更改为runnable 。 线程状态仅在操作系统实际计划将其执行时才变为运行状态(从runnable )。

典型的操作系统支持两种线程模型-合作和抢占。 在协作模型中,每个线程都将最终决定对CPU的保留时间以及何时放弃。 在此模型中,由于恶意线程可能永远不会放弃控制,因此其他线程可能永远都不会运行。 在抢占模型中,OS本身在时钟“滴答”上使用计时器,可以在该计时器上将控制从一个线程突然转移到另一个线程。 在这种情况下,决定哪个线程接下来将获得控制权的调度策略可以基于各种标准,例如相对优先级,特定线程已经等待执行多长时间等。

如果处于运行状态的线程由于某种原因决定Hibernate,需要等待资源(例如,输入数据到达设备或通知已设置某些条件),则可以进入阻塞状态,或在尝试获取互斥锁时被阻止。 当睡眠期到期,预期的输入到达或互斥锁的当前所有者释放该互斥锁并通知等待线程该互斥锁已准备就绪时,被阻塞的线程会重新进入可运行状态。

线程在其run()方法完成时终止,方法是正常返回或引发未经检查的异常(例如RuntimeException 。 此时,线程的状态为dead 。 一旦线程失效,就无法通过重新调用其start()方法来重新start() ,因为这样做将引发InvalidThreadStateException

四个常见的陷阱

如我所展示的,Java语言支持的许多精心设计的构造促进了Java语言的多线程编程。 此外,还设计了许多设计模式和准则,以帮助您避免这一复杂任务的许多陷阱。 尽管如此,很容易在无意中将微妙的错误引入多线程代码中,更重要的是,此类问题也很难分析和调试。 下面列出了您尝试使用Java语言进行多线程编程时将遇到(可能已经遇到)的最常见问题。

比赛条件

当多个线程之间存在对共享资源的争用并且获胜者确定系统的行为时,系统中就会存在竞争条件 。 Allen Holub在他的文章“在现实世界中编程Java线程”中提供了一个非常简单的多线程程序示例,并带有此错误(请参见参考资料 )。 冲突的访问请求之间不正确同步的一个甚至更隐蔽的后果是数据损坏 ,其中共享数据结构部分地由一个线程更新,而部分地由另一个线程更新。 在这种情况下,它不是按照获胜线程的意图来表现系统,而是根据两者都不是,因此两个线程最终都会失败。

死锁

死锁是一种条件,其中线程由于正在等待某个条件变为真(例如资源可用)而永远被阻塞,但是由于该条件变为真的线程被阻止,因此该条件无法变为真。轮到,等待第一个线程“做某事”。 这样,两个线程都在等待对方迈出第一步,而两个线程都无法做任何事情。 阅读艾伦赫鲁伯的文章(请参阅相关信息 )对于如何死锁在多线程Java代码中发生的例子。

活锁

与死锁不同, 活动锁发生在线程实际运行时,但是没有完成任何工作。 这通常在两个线程交叉工作时发生,因此第一个线程所做的工作被另一个线程撤消。 一个简单的示例是,每个线程已经拥有一个对象,而另一个线程又需要另一个对象。 现在想象一个情况,其中每个线程放下它拥有的对象并拾取另一个线程放下的对象。 显然,这两个线程可以永远以锁步方式运行,从而有效地实现了任何目标。 (一个现实世界中常见的例子是,两个人在狭窄的走廊里互相靠近。每个人都试图向一侧移动以让另一侧通过,从而保持礼貌,但是两个人同时保持向同一侧移动,因此确保两者都无法通过。这种情况持续了一段时间,并且两者都左右摇摆,并且没有取得任何进展。)

资源匮乏

资源匮乏 (也称为线程饥饿 )是Java语言的wait / notify原语不能保证活动性的结果 。 这些方法必须为正在等待或通知的对象保持锁。 在特定线程上调用的wait()方法在开始等待之前释放了监视器锁定,并且必须在从该方法返回后通知之前重新获取它。 因此,除了锁本身之外,Java语言规范(请参阅参考资料 )还描述了与每个对象关联的等待集 。 一旦线程释放了对象上的锁(在调用wait ),它将被放置在此等待集中。

大多数JVM实现将等待线程放在队列中。 因此,如果有其他线程在发生通知时正在等待监视器,则新线程将被放置在队列的后面,并且不会是获取该锁的下一个线程。 因此,在通知线程实际获取监视器时,通知线程的条件可能不再成立,因此必须再次wait 。 这可以无限期地继续,从而导致浪费的计算工作量(由于将线程分派到等待集中和从等待集中移出)和线程匮乏。

贪婪的哲学家的寓言

证明这种行为的原型示例是“ Wot,No chickens?”。 Peter Welch教授描述的问题(请参阅参考资料 )。 在这种情况下,正在考虑的系统是由五位哲学家,一名厨师和一个食堂组成的大学。 除了一个人以外,所有哲学家都思考了一会儿(在代码示例中为三秒钟),然后去食堂吃饭。 这位“贪婪的”哲学家不想浪费任何时间思考-而是一次又一次回到食堂,希望能吃到鸡。

厨师分四批烹饪鸡肉,每批准备就绪后可补充食堂。 尽管不断去厨房,但这位贪婪的哲学家总是错过食物! 这就是发生的情况:他第一次到达那里,还为时过早,厨师还没有做饭。 这导致贪婪的哲学家被搁置(通过wait()方法调用)。 当释放时(通过notify()方法调用),他又被放回了食堂队列。 但是,在他被拘留期间,他的其他四个同事已经到达,因此他在食堂队列中的位置落后于所有人。 同事带走了刚从厨房赶来的全部四人,贪婪的哲学家再次被搁置。 可悲的是(或者也许是公正的),他从来没有退出过这个周期。

验证问题

通常,很难用正式规范来验证用Java代码编写的多线程程序。 也无法轻易开发出自动化工具来对常见的并发问题(例如死锁,活动锁和资源匮乏)进行完整且可靠的分析-尤其是在任意Java程序中并且没有正式的并发模型时,尤其如此。

更糟糕的是,并发问题反复无常且难以追查这一事实。 每个Java开发人员都听说过(或自己写过)一个经过严格分析并且可以正常运行且长时间未出现潜在死锁的Java程序。 然后有一天,突然,问题决定解决,开发团队花了许多不眠之夜试图找到并解决根本原因。

一方面,多线程Java程序容易出现错误,这些错误看起来不那么明显,并且可能在看似任意的时间发生。 另一方面,这些错误很可能不会出现在您的程序中。 问题在于不知道。 多线程程序的复杂性使其难以有效验证。 没有一套完善的规则可以在您的多线程代码中发现此类问题,也不能最终证明它们的存在,这导致一些Java开发人员完全回避设计和开发多线程应用程序,即使这样做对根据并发性和并行性对系统进行建模。

开发商谁尝试做多线程编程通常满足于一个或两个以下的,最好的部分,解决方案:

  • 长时间测试代码,找出所有并发问题,并热切希望在应用程序上线之时就已发现并解决所有此类问题。
  • 充分利用为多线程编程建立的设计模式和准则。 但是,只有在整个系统都按照其规范进行设计时,此类准则才有效,并且任何设计规则都无法涵盖所有​​类型的系统。

尽管鲜为人知,但还有第三种选择来编写(然后验证)正确的多线程应用程序。 死锁和活动锁之类的问题最好在设计时使用称为通信顺序过程(CSP)的线程同步的精确数学理论来处理。 CSP由CAR Hoare在1970年代后期开发,它提供了一种有效的手段来证明使用其构造和工具构建的系统没有并发的常见问题。

第1部分的结论

在针对Java程序员的CSP的全面介绍的第一部分中,我重点介绍了克服多线程应用程序开发中的常见问题(即理解它们)的第一步。 我向您介绍了Java平台上多线程编程当前受支持的结构,解释了它们的起源,并讨论了此类程序可能存在的问题。 我还解释了在任意,大型和复杂的应用程序中应用形式理论来清除或证明不存在这些问题(即种族危险,死锁,活锁和资源匮乏)的困难。

在第2部分中 ,考虑到此基本框架,我向您介绍CSP及其基于Java的实现JCSP库。 您会发现,CSP是一种复杂的数学理论,具有许多强大的应用程序(我将在第3部分中讨论一些更高级的应用程序),包括解决多线程编程中的常见问题。

要了解JCSP库如何将CSP的本质提炼成一个易于理解的Java结构框架,请立即跳至“ 第2部分:使用JCSP进行并发编程 ”。

致谢

我要感谢在撰写本系列文章期间我从Peter Welch教授那里得到的鼓励。 尽管他的日程安排很忙,但他还是花了一些时间对草案版本进行了非常彻底的审查,并为提高系列的质量和准确性提供了许多宝贵的意见。 所有其余错误都是我的! 我在文章中使用的示例基于和/或派生自Javadocs中针对JCSP库和/或JCSP网站上提供的Powerpoint演示幻灯片的文档。 这两种资源均提供了大量可供探索的信息。


翻译自: https://www.ibm.com/developerworks/java/library/j-csp1/index.html

java csp_Java程序员CSP,第1部分相关推荐

  1. java csp_Java程序员CSP,第2部分

    CSP是用于对并发对象之间的复杂交互进行建模的范例. 使用CSP的主要优点之一是能够精确地指定和验证程序每个阶段涉及的对象的行为. CSP的理论和实践对并发设计和编程领域产生了深远的影响. 它是occ ...

  2. Java高级程序员(5年左右)面试的题目集

    Java高级程序员(5年左右)面试的题目集 https://blog.csdn.net/fangqun663775/article/details/73614850?utm_source=blogxg ...

  3. 为什么3年的Java高级程序员薪水仅仅8k-10k,而一个Linux底层C语言程序员两年经验就敢要1...

    为什么80%的码农都做不了架构师?>>>    为什么3年的Java高级程序员薪水仅仅8k-10k,而一个Linux底层C语言程序员两年经验就敢要10k的薪水?   由于目前国内嵌入 ...

  4. 如何才能成为java高级程序员?

    身为程序员,一旦进入技术行列,就开启了持续学习的道路,更迭迅速的互联网时代,技术自然也是一代一代的更新,在技术进阶的道路上,要不断吸收新的想法和技术知识. 牛逼的人总是让人羡慕,但如何才能让自己成为牛 ...

  5. 重庆找Java开发工作_重庆【Java开发程序员】

    重庆[Java开发程序员],提倡一切为了学员就业的办学思想,教学过程中坚持以练习企业项目为主,让学员真正能学到技术,毕业就能适应工作岗位. 重庆[Java开发程序员], Java 编程开发.而且很多软 ...

  6. 做为一名java高级程序员,需要了解哪些岗位?

    一.Java高级程序员 要想成为JAVA(高级)程序员也称Java高级工程师,肯定要学习JAVA.一般的程序员或许只需知道一些JAVA的语法结构就可以应付了.但要成为JAVA高级程序员,您要对JAVA ...

  7. java前沿技术_互联网百强企业架构师告诉你,Java应该这么学!云和数据超全面Java中级程序员学习路线图重磅发布!...

    作为常居编程语言排行榜第一名的编程语言,Java语言以其稳定性.健壮性著称,是一门非常成熟的编程语言,多年来一直是国际上众多企业的首选编程语言. Java语言不仅吸收了C++语言的各种优点,还摒弃了C ...

  8. @Java web程序员,在保留现场,服务不重启的情况下,执行我们的调试代码(JSP 方式)

    阅读目录 一.前言 二.问题描述 1.问题代码 2.jsp文件代码 3.执行 jsp 三.总结 回到顶部 一.前言 类加载器实战系列的第六篇(悄悄跟你说,这篇比较水),前面5篇在这里: 实战分析Tom ...

  9. 为什么阿里巴巴最爱招Java开发程序员?

    为什么阿里巴巴最爱招Java开发程序员?因为java本身设计特性就是大规模工程语言. 它有三个根本性的特征 1.适应各种业务,你目前知道的几乎所有的业务都可以用java写.有很多语言做不到这一点. 2 ...

最新文章

  1. 导出全部记录到excel
  2. jQuery获取自身HTML
  3. github博客安装jekyll的RUBY更换源
  4. oracle-DECODE()函数
  5. 阶段5 3.微服务项目【学成在线】_day04 页面静态化_15-页面静态化-模板管理-模板管理业务流程...
  6. 基于java+springboot+mysql的校园二手交易平台
  7. dhcp服务器日志文件,dhcp服务器日志查看
  8. Linux下载HTTP文件
  9. LSF集群作业管理系统
  10. commons-math3-3.6.1-org.apache.commons.math3.analysis.function-包下的类(二)-中英对照文档及源码赏析
  11. 数据挖掘——决策树和K近邻
  12. 计算机的基本配件图片,计算机硬件的重要部件
  13. IC授权卡和复制卡的区别_信用卡小知识【芯片卡磁条卡的区别】
  14. 交易的核心精髓是什么?
  15. android连接php的url,android编程-解析URL-类同php的parse_url函数
  16. (转)DDD CQRS和Event Sourcing的案例:足球比赛
  17. Python图像库PIL的类Image的paste写法
  18. 前序遍历+中序遍历重建二叉树
  19. win10系统下找不到hosts文件解决方案
  20. 4.MyBatis源码解析-MyBatis扩展点--阿呆中二

热门文章

  1. 弄潮儿数据_大数据时代,你如何成为弄潮儿
  2. MSSQL2005差异备份拿shell MSSQL2005 Backup Get Shell
  3. 排列组合 n个球放入m个盒子m问题 总结
  4. 美白,去斑,去黄,调理,中药面膜粉
  5. web前端开发技术实验与实践(第三版)储久良编著 课外拓展训练1.2 设计彩色页面
  6. ascii码占多少个字节
  7. 【学习笔记】市场机制设计
  8. insert into 的两种表复制插入方法
  9. 1429: 箭无虚发
  10. Scratch3.0创意编程(基础篇):第7课 打地鼠