位于上海,服务全国!

位于上海,服务全国!

理解Java线程模型

作者:admin 分类: 时间:2016-12-08 16:41:54 点击量:2758


Java程序中的线程作为独立的执行路径异步运行。 它基本上被设计为与同一程序的其他子集同步执行的代码子集。 线程背后的动机是利用并发。 但问题是,使用并行逻辑的程序不仅难以概念化,而且难以一致地实施。 即使我们以并行模式工作设计逻辑,我们也不能确定它的一致性。 代码的并发执行引起许多问题,例如同步,死锁,原子性,资源共享等等。 这篇文章略微迂回探讨普通和特殊的Java线程模型。


线程作为进程
操作系统演变为一次运行多个进程。 这些进程以孤立的,独立的方式执行,资源被分配给这些进程,例如存储器,文件句柄和安全证书。 有时,一个进程通过诸如套接字,信号,共享存储器,信号量和文件之类的通信机制与另一个进程通信。 线程也非常类似于一个进程,但运行于较小的规模,有时称为轻量级进程。 它使得程序控制流的多个流在一个进程内共存。


非顺序执行的问题
正常的Java代码以顺序方式执行。 当我们应用线程机制时,我们实际上是应用一个调整,否则以直接顺序模式编程。 这个调整分叉(读取n-bifurcate)代码在一个并行环境中运行,其中每个执行路径称为线程。 在一个多线程环境中,必须确保一个线程必须独立执行并且独立于其他线程形成最小化依赖性,因为依赖性可能完全停止执行。 有时,两个或更多资源竞争一个资源。 这可能导致竞争条件或死锁。 为了克服这种情况,我们可以粗略地想出两个方法:

防止一个进入这种情况。
仅在出现问题后才解决。

多重编程的问题如果不同步可能是相当有害的。 线程的分开执行是一个理想情况,但是实际上线程之间的交互是必要的,特别是当一个线程需要另一个线程使用的资源时。 在这种情况下,请求线程必须足够绅士的等待另一个线程使用的资源释放。

关于线程安全

在诸如Java之类的命令式编程语言中,创建并行逻辑并不是那么容易,不需密切考虑线程安全性的问题,因为它不是语言设计的固有原则。 代码必须以线程安全的方式明确设计。 没有严格的规则来确保线程的安全,但考虑以下指针可能有助于创建一个。


不可变对象默认情况下是安全的线程,因为一旦创建,它们的状态是不可修改的。
Java中的最终变量是线程安全。
最小化多个线程之间的共享或对象。
在运行共享资源时应用锁定。
一些设计保持线程安全的类是String,Hashtable,ConcurrentHashMap等。 有关这些类的更多信息,请参阅Java API文档。


静态变量,除非明确同步,否则是不安全操作的潜在漏洞。
原子操作是线程安全的;例如,a + 1是原子,但a ++不是,因为a ++意味着a = a + 1,并且由两个操作+(加法)和=(赋值)组成。 但是,有一种方法可增量操作原子,但得通过AtomicIntegeras的帮助,如下:
AtomicInteger aInt=new AtomicInteger(5);
// ...
aInt.incrementAndGet();.
对于其他有类似类的数据类型,例如AtomicBoolean,AtomicLong,AtomicLongArray等。
局部变量是线程安全,因为每个段都有自己的副本。
易失性关键字可用于确保线程不缓存变量。
多线程编程的问题是它同时提供并发和破坏的力量。 如果我们邀请线程机制,我们不能忽视它的陷阱。 在像Haskell这样的纯函数式语言中创建死锁非常困难。 但是,如果我们真的想从现代机器中的多个CPU(或多核CPU)中提取出工作,则没有其他方法可以接受线程,即使有其陷阱。


今天的软件不再简单。 除了服务其实际目的之外,程序必须同时是漂亮,用户友好,安全,并且频繁地更新(读取:修补,主要是由于实现漂亮的故障等)。 观察今天的操作系统。 已投入最大代码以增强系统的外观和感觉。 为了利用这些所谓外观的性能,机器需要原始硬件功率,同时软件必须能够采用一切可能的机制来利用这种功率并有效地使用它们。 然而,如果我们减去这些冗余代码,这个大软件将仍然极具有价值,以MBs为大小。 不仅在大小上,为实现并发的竞争需求,代码变得越来越复杂,容易滋生bug。 因此,很难忽略并行计算的必要性,以及在软件端利用的能力。 一个谨慎的词;在设计并行逻辑时应该非常小心,以便在执行时保持应用程序的完整性。 虽然Java API在这方面有一些很好的支持,例如Fork / Join框架,以帮助程序员克服麻烦,但是复杂性和机会仍然存在。 fork / join框架有助于使用多线程应用程序自动扩展以利用多核环境。


让我们得到一个基本的Java内存模型的构想,因为为了理解Java线程模型,是有必要了解至少一些JVM(Java虚拟机)内存布局的事项。 毕竟,内存是线程的运动场。


JVM是一个在实际操作系统之上运行的系统。 它有自己的内存管理方案,与底层平台的内存体系结构同步运行。


Java内存管理器指定在访问共享变量时线程如何与同步过程一起工作。 它将内存分为两部分: 堆栈区域和堆区域。

线程

 

Figure 1: The Java thread manager

每个正在运行的线程在堆栈区域中创建自己的堆栈;这个栈包含线程的所有特定信息或本地信息,例如所有声明的原始变量和方法调用。 该区域在线程之间不可共享,并且根据线程的运行条件动态地改变堆栈大小。


堆区域用于存储Java应用程序创建的对象。 这些对象可以被所有线程共享,并且如果它有一个对象的引用可以访问对象的方法。 在两个或更多线程调用的情况下,共享对象方法或其成员变量,每个获得它的副本,并将其存储在其本地堆栈区域中。


Jakob Jenkov撰写了一篇有关线程和Java内存模型相关各种问题的文章。 有关更多详细信息,请参阅。


线程优先级
一个线程可以存储在于多个状态中,例如在其活动中暂时运行或暂停,这可以稍后从其停止的点恢复。 一个线程在等待资源时处于阻塞状态。 一个线程可以随时终止。 Java使我们能够在使用多线程时分配优先级。 优先级由整数指定。 它基本上决定何时从一个正在执行的线程切换到另一个线程;这被称为上下文切换。 决定线程之间的上下文切换的规则如下:


线程可以通过在挂起的I / O操作上显式生成,休眠或阻塞来自愿放弃其控制。 在这种情况下,一个最高优先级的线程获得CPU插槽。
更高优先级的线程可以抢占任何其他低优先级线程,并占有CPU插槽,无论线程在那一刻做什么。 它将被暂停以让路更高优先级的线程。
显而易见,在单个运行线程的情况下不会出现优先级的问题。 但是,当存在具有相同优先级的竞争线程时,情况就有点棘手。 它实际上取决于底层平台来决定谁获得第一个CPU插槽。 例如,在Windows中,相同优先级线程以循环方式获得相等的机会。 其他系统可以实施不同的规则,诸如一个或多个等优先级线程自主地对其对等体产生控制。


同步
Java线程实施异步行为;因此,它们必须显式同步,特别是在处理共享资源时。 例如,当我们知道执行线程可能竞争单个资源时,我们必须将它们包含在同步块中,以确保不会出现冲突情况。 应用一个被称为监视器的流行技术。 一个监视器可以被认为是一个控件,只允许一个线程执行一次。 Java在这方面很容易,因为每个对象都有自己的监视器。 一旦一个线程进入同步块,其就可确保不会发生冲突。


线程之间的通信
Java线程不依赖于一个底层平台来建立线程之间的通信。 相反,他们使用一个优雅的机制,通过由对象定义的预定义方法,如wait(), notify()和notifyAll()。 有些方法在Object类中声明为final。 结果是,所有Java类都可以使用它们;然而,它们只能在一个同步的上下文中调用。 规则很简单:


结论
这是对Java线程模型的初步介绍;当我们更深入研究时,会发现有更多的功能。