在本专栏中学习注释,以组织机械行业出版社的“ Java并发编程战斗”。
该过程是资源分配的最小单元,线程是CPU执行的最小单元。程序计数器(程序计数器),操作多个堆栈和本地变量表。请参阅作者的JVM注:JVM:Java Memory区域部门-juejin.cn和JVM:Bytecode Execution Exectement -Juejin.cn。
在同一过程中共享相同地址空间的多个线程,它们可以在同一内存空间中访问和修改变量。没有设置任何协同机制,所有线程都将独立运行。EAVER是“自私的”:当它修改某个变量时,它不会考虑其他线程是否会读取或修改相同的变量。
要构建一个稳定的并发程序,必须正确使用线程和锁,这仅用于实现并发编程的某些机制。要编写线程安全代码,核心是共享和变量(可变)状态的访问控制。
当我们使用“状态”一词来描述域(或成员变量,属性等)时,这意味着它是一个变量变量,而不是货币值。此外,该状态包含其字段所描述的所有数据。例如:哈希图域的状态不仅包括其自己的参考,还包括哈希图中所有条目的状态。
如果状态是安全的,则必须引入锁定机制来保护它。Java提供各种关键字,原子变量,显示锁和其他工具以实现目的。本章将介绍前两个。
当我们讨论线程安全性时,它实际上是并发环境中该过程的正确性。扩展是:某个对象的行为和规范是完全相同的。我们将定义两个规格:
并非所有并行过程都必须考虑并发安全问题。如果多个线程在不共享任何状态的情况下操作不同的对象,则此代码实际上是并行的,无流动的。
在控制的大规模系统中,通常被认为是并发问题,因为该系统取决于各种全局可变维护状态;除了基于维护的维护并发系统外,还有一个基于参与者模型的并发系统,例如Akka(Akka(Akka(Akka)(Akka(Akka(Akka(Akka),Akka(Akka(基于Actor -Base -Base -Base -Base -Base -Basecorrent)比锁定更容易基于锁定的同步控制)在大型计算系统中,它更倾向于并行调度,例如Spark。
此外,未态对象必须是线程-SAFE.NO状态对象不包括任何域,也不包含对其他域的任何引用。
请参阅下面的方法:它是一个独立的封闭本身,并且不会在内部引用任何自由变量。线之间的状态,因此它不会互相干扰以计算结果。
通常,如果要确保变量安全,则有三种方法:
显然,如果一个对象在单个线程中不正确,则不得安全。
安全探索“永远不会发生坏事”,主动讨论“正确的事情应该尽快发生。锁。如果线程B尚未发布资源,那么线程A只能保持等待。活动问题包括死锁,饥饿,活着的锁等。引起活动问题的错误很难分析,因为这取决于序列在不同线程中发生的事件,因此在单位测试中很难再现。
在设计井设计的并行过程中,多线程可以提高程序的性能,但是多线程的使用将不可避免地带来更多的操作费用。当调度程序暂时挂起活动过程并运行另一个线程时,上下文开关需求要执行。
如果平行时间表不正确设计,则CPU将被迫花费大部分时间在上下文上切换而不是执行任务。在同一时间,当线程之间的共享变量之间,必须采用同步机制,这些机制以及这些机制将抑制某些编译器的优化,使内存缓冲区的数据无效。可以看出,活动问题也与性能问题密切相关,并且这些内容在随后的学习中逐渐讨论。
如果上一个示例的字段移至实例域,则将携带由类创建的对象。
目前,该方法是一个自由变量(它是一个不受限制的变量)。悬浮到字段表(而不是本地变量表),可以通过多个线程观察和修改相同的对象。
在单个线程环境中,这是一个正常的,完全合理的代码。但是在多线程环境中这样做可能会丢失一些更新记录。这是一个紧凑的语法,因此看起来像是一个操作,但实际上它并非如此:它包含:“读→修改→写”三个独立的步骤,每个步骤取决于上一步的状态。
想象一下,这两个线程是同时读取的,然后执行了一个增量操作,然后两个线程同时将值修改为1。显然,计数器在此处偏离。,柜台的偏差将变得更大且更大。由于执行时间不当而导致结果不正确,因此有正式名称。
当某个计算的正确性取决于多线交替的顺序时,竞争条件发生。Poppodulation:Program的正确性取决于运气。最常见的竞争条件出现在“检查”法规中ACT”,因为线程可能会根据失败的观察结果执行下一步。
例如,线程观察到开始时不存在文件x,并计划创建一个新的文件x并编写一些内容。该线程等同于失败)。这可能导致各种问题:不可预测的异常,数据覆盖范围等。
典型的场景延迟初始化“首先检查然后执行”。延迟初始化的最初意图是,在真正需要时,延迟创造成本的高成本的目标被延迟。
显然,这是一个“首先检查然后执行”的过程:当线程对访问方法的访问时,它将带头观察是否为null,然后决定是直接初始化或直接返回引用; EssenceBut现在,是否观察到的实际情况取决于不可预测的序列,还包括线程A的初始化需要多长时间。如果线程B意外错误地判断,则整个过程将错误地创建两个示例。
附件:首先加载类时,将执行其静态域。该功能可用于实现线程安全的单个示例。
为了避免竞争条件,必须引入原子操作。例如,两个线程A和B正在同时修改相同的状态。当线程启动操作时,另一个线程要么具有访问→修改→写入过程的完整执行,要么尚未开始。这次,这两个线程的修改状态操作是原子。在访问和修改同一状态时。
如果状态的操作是原子,则将不存在原始竞争条件。我们将收集访问的三个过程→修改→修改这三个过程作为复合操作,并将此复合操作转换为原子操作以确保线程以确保线程安全。
以下包含一些用于改善普通数值类型的原子变量。在此示例中,所有可以保证为Atom的操作。并且由于当前仅维护一个状态,此时,可以称为类本身是安全的。
线程安全目标的管理状态尽可能使并发任务的代码更容易维护。
假设在当前类中定义了更多的状态,复合操作变得更加复杂,但是仅添加更多原子变量,这足以说明这个问题,仍然将银行转移用作示例,并且两个状态的两个状态维持两个帐户:A,B。两个帐户的余额。
线程安全性规定,无论哪个时间交替执行多个线程的操作,它都无法破坏类的非变异性条件。例如,转移业务中不变条件之一是:在开始时任何线程开始时观察到的两个帐户应相等。
尽管两个参考且两者都是安全的,但同类中仍然存在竞争条件,这可能会导致错误,因为线程无法同时修改两个帐户的数量。例如,另一个线程T2进入T1线程”仅写入帐户A但未写入帐户B”,并且可能无法确定其观察到的不变性标准。
因此,如果要保持状态的一致性,则需要在单个原子操作中更新所有与状态相关的状态变量。
Java支持Grammar:关键字(也称为构建的lockssss.it)的原子性。它可以单独用作语法块,例如:
任何Java对象都可以用作互斥的同步锁。在输入同步代码块之前,必须先锁定线程,然后在退出同步代码块时(包括丢弃异常出口)。要获得T1持有的锁定对象,必须阻止并等待。如果未释放T1,T2将无限期地等待。通过此表单,Java保证只有一个线程可以同时执行同步代码块。
您可以将受保护的变量直接放入其中。目前,据说该州已被锁定;或可以将整个对象(此)放在其中。在这个时光
关键字也可以直接在方法语句上标记。标记的方法等同于整个函数主体上的同步代码块,而代码块的锁定是呼叫方法本身的对象(这)。如果是一个静态同步方法,它将使用对象的类信息的对象作为锁。
锁定操作的粒度是线程,而不是呼叫。在Java中,线程可以反复访问其固定的锁。该特征称为锁的重量。
实现方法是为每个锁定设置一个计数器并记录固定螺纹。可以重复输入持有人的线程,并累积每个计数器;每次发布时,计数器都会减少1.计数器的值为0时,这意味着锁定当前释放。
显然,当调用对象方法时,线程需要在此对象上两次锁定对象。如果Java将锁定锁定到不可替代的情况下,则以下代码将生成僵局。
一个常见的锁定协议是将所有可渗透状态封装在对象内部,然后通过对象的锁定锁定在对象中的所有可访问的代码块,必须小心,如果错过某个地方的同步规则,整个锁定协议将失败。
对于原子操作中涉及的多个变量,必须保护相同的构建锁。例如,和平转移业务受到对象本身的锁定的保护。
并非所有数据都需要保护锁。仅通过多个线程访问,它才需要锁定,这也涉及锁定粒径的问题。修理购买的单位价格和数量将在转移之前暂时计算:交易金额:
由于构建的锁添加到该方法中,因此多个线程必须在一个系列中工作(假设此处仅使用一个对象),并且整个代码的执行性能将非常糟糕。我们实际上注意到它是一个本地变量不会由任何线程共享,因此根本不需要加入同步代码块。以下是优化代码:
这样,即使线程发现无法立即修改A和B帐户的状态,也可以计算传输的传输数量,而不是等待其他线程在计算之前完成完整的执行尤其是当某些局部变量需要大量操作时,精制的阻塞颗粒可以在性能和安全性之间找到良好的平衡。
如果锁定太长,则性能问题将变得非常重大。因此,不要认真的时间 - 同步代码块内部的操作。
