(2)マルチスレッド化と高度同時スレッドセーフ

スレッドの基本的な考え方、およびマルチスレッドの作成と操作:前は、マルチスレッドのいくつかの基本を学びました。内容は比較的簡単ですが、そうでない場合、我々は、マルチスレッドがちでセキュリティスレッドので、学ぶためにそんなに心を費やす必要はありません、その単純ではない、確かな知識をマルチスレッド。

だから、次のように定義され、それはスレッドセーフです:

複数のスレッドは、オブジェクトの振る舞いを呼び出して、ランタイム環境と交互運転中のスレッドのスケジューリングを考慮しないと、追加の同期を必要としない、または連携呼び出し側に他の操作を実行した場合、同じオブジェクトにアクセスするときあなたは、このオブジェクトがスレッドセーフであると、正しい結果を得ることができます。

これは単に、マルチスレッドコードで予想される動作の結果が正しく矛盾した結果であり、問題のスレッドの安全性は、一般的に構成されていることを意味し、メインメモリ及び作業メモリのデータの不整合および並べ替えに起因します。

最初のJavaのメモリモデルを理解する必要があり、これらを理解するために。

Javaのメモリモデル

並行プログラミングの分野では、二つの重要な質問がありますスレッド間通信の同期

1.1通信および同期

スレッド間通信は、スレッド間で情報を交換するためにどのような機構を指す、プログラミングコマンドを実行し、2つのスレッド間通信機構メモリ共有、メッセージパッシングを

共有メモリの同時実行モデルでは、一般的な状態は、書き込むことによって、スレッド間のスレッド間のプログラムを共有 - 暗黙通信、共有メモリ通信の典型的には、共通の目標状態を共有することによって通信するメモリを読み取ります。

同時実行モデルのメッセージングにおいて、状態間には共通のスレッドが存在しない、明示的に()(Javaのメッセージ待ちである典型的な実施形態では、スレッド間の明示的なメッセージを送信することによって通信する)と通知されなければなりません。

スレッドの同期は、スレッド間の相対的な順序を発生する機構の動作を制御するためのプログラムを指します。共有メモリの同時実行モデルでは、明示的な同期が実行されます。プログラマが明示的メソッドまたはスレッド間の相互排除を実行するために必要なコードの一部を指定しなければなりません。同時実行モデルのメッセージングでは、以降のメッセージを受信する前に送信メッセージは、したがって暗黙の同期が実行されなければなりません。

Javaのメモリモデルがあり、同時共有メモリモデル主にスレッド間で読み込むことで、 -共有変数を暗黙の通信の書き込みを完了させます。あなたが理解できない場合はJavaの共有メモリモデルは、並行プログラムを書くときに、メモリの可視性に関するさまざまな問題が発生します。

1.2 Javaのメモリモデル(JMM)

処理速度とメインメモリは、CPUの速度を読み書き桁ではない、この大きなギャップをバランスさせるために、各CPUのためにそこにキャッシュされます。したがって、第一のメインメモリ上の共有変数が、各スレッドはそれ自身のワーキングメモリを有しており、配置されている共有変数は、それ自身の作業メモリのメインメモリにコピーされ、読み出しおよび書き込み動作は、次に、ワーキングメモリに使用されています変数のコピー、およびいくつかの点で、可変ワーキングメモリのコピーがメインメモリにライトバックされます。JMMは、抽象化のレベルから、このように定義され、JMMは、他のスレッドが表示されている場合、共有変数スレッドを書くことにしました。

図JMMの抽象が通信を完了するために、スレッドAおよびスレッドBとの間に、模式図であり、次いで、次の2つのステップを含みます:

  1. スレッドの後にメインメモリから共有変数は、バックメインメモリにデータを再書き込みした後、スレッドA及び操作の作業メモリに読み込まれます。

  2. スレッドBは、メインメモリから最新の共有変数を読み込み

見て、水平、スレッドAおよびスレッドBから共有変数を介して暗黙の通信を行う場合。スレッドがタイムリーに更新されたデータは、スレッドBが「ダーティリード」現象に登場古いデータを、読まれる時に、メインメモリにライトバックされていない場合、この1は、非常に興味深い質問があります。同期機構によって(異なるスレッド間で発生する動作の相対的な順序を制御する)、またはそれぞれが各スレッドが表示されるように、メインメモリをリフレッシュするように強制することができるような揮発性キーワード揮発性変数によって解決しました。

:多くのJavaのメモリモデルの内容は、この記事を参照することをお勧めしますhttps://blog.csdn.net/suifeng3051/article/details/52611310を

1.3 可见性和竞争现象

当对象和变量存储到计算机的各个内存区域时,必然会面临一些问题,其中最主要的两个问题是:

  1. 共享对象对各个线程的可见性

  2. 共享对象的竞争现象

共享对象的可见性

当多个线程同时操作同一个共享对象时,如果没有合理的使用volatile和synchronization关键字,一个线程对共享对象的更新有可能导致其它线程不可见。

一个CPU中的线程读取主存数据到CPU缓存,然后对共享对象做了更改,但CPU缓存中的更改后的对象还没有flush到主存,此时线程对共享对象的更改对其它CPU中的线程是不可见的。最终就是每个线程最终都会拷贝共享对象,而且拷贝的对象位于不同的CPU缓存中。

要解决共享对象可见性这个问题,我们可以使用volatile关键字,volatile 关键字可以保证变量会直接从主存读取,而对变量的更新也会直接写到主存,这个后面会详讲。

竞争现象

如果多个线程共享一个对象,如果它们同时修改这个共享对象,这就产生了竞争现象。

线程A和线程B共享一个对象obj。假设线程A从主存读取Obj.count变量到自己的CPU缓存,同时,线程B也读取了Obj.count变量到它的CPU缓存,并且这两个线程都对Obj.count做了加1操作。此时,Obj.count加1操作被执行了两次,不过都在不同的CPU缓存中。

要解决竞争现象我们可以使用synchronized代码块。synchronized代码块可以保证同一个时刻只能有一个线程进入代码竞争区,synchronized代码块也能保证代码块中所有变量都将会从主存中读,当线程退出代码块时,对所有变量的更新将会flush到主存,不管这些变量是不是volatile类型的。

二 重排序

指令重排序是指编译器和处理器为了提高性能对指令进行重新排序,重排序一般有以下三种:

  1. 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

  2. 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

  3. 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

1属于编译器重排序,而2和3统称为处理器重排序。这些重排序会导致线程安全的问题,JMM确保在不同的编译器和不同的处理器平台之上,通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序,为上层提供一致的内存可见性保证。

那么什么情况下一定不会重排序呢?编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序,这里有个数据依赖性概念是什么意思呢?看如下代码:

int a = 1;//A
int b = 2;//B
int c = a + b;//c

这段代码中A和B没有任何关系,改变A和B的执行顺序,不会对结果产生影响,这里就可以对A和B进行指令重排序,因为不管是先执行A或者B都对结果没有影响,这个时候就说这两个操作不存在数据依赖性,数据依赖性是指如果两个操作访问同一个变量,且这两个操作有一个为写操作,此时这两个操作就存在数据依赖性,如果我们对变量a进行了写操作,后又进行了读取操作,那么这两个操作就是有数据依赖性,这个时候就不能进行指令重排序,这个很好理解,因为如果重排序的话会影响结果。

这里还有一个概念要理解:as-if-serial:不管怎么重排序,单线程下的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

这里也比较好理解,就是在单线程情况下,重排序不能影响执行结果,这样程序员不必担心单线程中重排序的问题干扰他们,也无需担心内存可见性问题。

三 happens-before规则

我们知道处理器和编译器会对指令进行重排序,但是如果要我们去了解底层的规则,那对我们来说负担太大了,因此,JMM为程序员在上层提供了规则,这样我们就可以根据规则去推论跨线程的内存可见性问题,而不用再去理解底层重排序的规则。

3.1 happens-before

我们无法就所有场景来规定某个线程修改的变量何时对其他线程可见,但是我们可以指定某些规则,这规则就是happens-before。

在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。

因此,JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM向程序员保证a操作将对b操作可见)。具体的定义为:

1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。

3.2 具体规则

具体的规则有8条:

具体的一共有六项规则:

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。

  2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作。

  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。

  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。

  5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作。

  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。

  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。

  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。

参考文章:

https://blog.csdn.net/suifeng3051/article/details/52611310

おすすめ

転載: www.cnblogs.com/yuanqinnan/p/11111788.html