状態から、マルチスレッドのJava並行処理の安全性のように見えます
Java開発者のために、不安のI ++同時性はよく知られているが、それは本当にとても安全ではないですか?
Javaコードを開発する際、同時マルチスレッド安全性を避けるためにどのような問題が発生しますか?これは、すべてのJavaプログラマが直面している問題です。この記事では、Javaコードのセキュリティコンカレントな設計上の考慮事項の開発のポイントを説明し、絵と紙が同時それぞれの場合にセキュリティ上の問題を議論し、Javaクラスのステータスを中心に展開します。同時の場合における状態変数のJavaクラスの性能を理解すると、マルチスレッドのJavaコードを書くときより、安全な堅牢で柔軟なマルチスレッドコードを書くための能力を知ることができます。
ディレクトリ
- マルチスレッドへ1.はじめに
- 状態2.は、Java並行処理、セキュリティのように見えます
- 3. Javaセキュリティ同時分解
- 3.1ステートレスクラス
- 3.2ステートフルクラス
- 3.3プライベートクラスのステータス
- 3.4株のクラスのステータス
- 3.5不変クラス状態(一定の状態)
- 3.6変数の状態クラス
- 3.7ノンブロッキング設計
- 3.8デザインをブロック
- 3.8.1リソースのデッドロック(リソースデッドロック)
- 3.8.2ロック・シーケンス・デッドロック(ロック順序デッドロック)
- 3.8.3開いた状態
- 4.静的な状態のクラス
- 外部の状態を、class安全同時マルチスレッディング
- 6.まとめ
- 7.デモ・コード
- 8.参照文献
マルチスレッドへ1.はじめに
現代のオペレーティングシステムにおいて、CPUスケジューリングは、各スレッドが独立してCPUタイムスライス命令を取得し、マルチスレッド化されたコードを実行するスレッドの基本単位です。同時に、同じプロセス内のすべてのスレッドが現在のプロセスのメモリアドレス空間を共有することになる、これらのスレッドは、現在のメモリアドレス空間で同じ変数にアクセスすることができます。変数を使用する場合、スレッド場合は、この変数を変更するには、別のスレッドは、マルチスレッドの同時実行の問題で予期しない結果を引き起こします。
アレイは、スレッドのサイクルを読み取る場合の単純な例は、削除されたオブジェクトのアレイ内の他のスレッドは、スレッドが読み取りの前にあるか、または不良データを読み出すことは汚れてもよいです。
コードの一部が正しく予想通りに行う、または予測不可能な最終結果を実行されていない場合、マルチスレッド実行では、我々は、このコードは、不安によって複雑になることを言います。換言すれば、予想されるように、その結果得られた操作データと所望の、安全な並行性が達成された場合、スレッド間でコードを実行します。
状態2.は、Java並行処理、セキュリティのように見えます
クラスの状態変数はパブリック変数、プライベート変数、または変数の静的および最終変更は、クラスの状態の異なる形態であるかどうか、クラスで宣言されていることを意味します。次のようにJava構文、様々な形でのクラス変数によると、
- パブリック変数(パブリック)、プライベート変数(プライベート)、変数保護(プロテクト)
- 静的変数(静的)
- 不変変数(決勝)
- 外部変数、内部変数、ローカル変数
実行時にこれらのクラス変数は、JVMのメモリは、さまざまなオブジェクトにマッピングされました。Javaセキュリティコンカレントデザインは、コアはその特性は、重要なJavaセキュリティコンカレント設計されている同時把握のパフォーマンスでこれらの変数に対処する方法です。
クラスのステータス、様々な状態のフォームの簡単な説明、および関連するセキュリティの同時Javaクラス変数から次の図の出発、
その中でも、
- 緑色のボックスは、マルチスレッド安全で示しています。
- オレンジの四角は危険なマルチスレッド示しているが、問題が発生します。
- フィギュアJavaクラスを参照するには、完全に、すなわち、オブジェクト指向の設計に基づいています:プライベート変数は、クラスメソッドは、唯一のクラスのメンバ変数の内部で動作してクラスメンバが宣言されています。
- 何の状態は任意のメンバ変数宣言せずにクラスを意味しません。Javaクラスは、パブリック、プライベートまたは変数によって保護され、またはその静的および最終的かどうか、メンバ変数の宣言を持っているに状態はいいがあります。
- プライベートステータスはクラスメンバ変数は、スレッドによって変数の割当てを達成するために、スレッドのThreadLocalによって分離されている手段と、共有状態は、クラス変数を指す複数のスレッドによってアクセスされることができます。
- 状態は不変クラスのメンバ変数をfinalとして宣言され、一定の状態です。
- 静的な状態は、静的として宣言されたクラスのメンバ変数を指します。
- ブロッキングは、スレッドがコードを実行する前に、あなたがロックを取得する必要がある場合、ロックはシリアルマルチスレッドコードを実行することによって実現唯一のロックであることを意味します。
この図は、並行オブジェクトのクラスのセキュリティを設計する方法を説明するための例として、Java言語に基づいていますが、実際には、状態の数値が関与、プライベート、不変の状態、非ブロックおよびこれらの概念へのアクセスをブロックしていることに留意すべきですそれは、より多くのオブジェクト指向プログラミング言語に適用されるべきです。
説明上の図は、各クラスの状態について1つずつ説明する、各状態における複雑な設計要素を導入します。
3. Javaセキュリティ同時分解
3.1ステートレスクラス
ステートレスクラスは、それがあることを意味任意の変数宣言のメンバーではない、例えば、
public class StatelessClass {
public void increment() { int i = 0; String msg = String.valueOf(i++); log.info(msg); } }
ステートレスクラスはスレッドセーフです。上記クラスの増分()メソッドは、2つのローカル変数iとMSG、2つのローカル変数がスタックメモリの別のスレッドによって空間、互いに分離するので、スタック空間ので、スタック空間法に割り当てられているあります変数は、スレッドセーフです。
あなたはまた、メソッド呼び出しに割り当てられた変数やオブジェクトを知ることができ、または変数がオブジェクト参照JVMのリリースがある場合は、スタック出口の後に(再び外部にアクセスすることはできません)、その後、変数やオブジェクトは、スレッドセーフです。ローカル変数とJVMスタック空間の詳細については、を参照してください。この記事。
3.2ステートフルクラス
代わりに、ステートレスとステートフルカテゴリがクラスを参照するメンバ変数を宣言している、例えば
public class StatefulClass {
private int i=0; public void increment() { i++; } }
上記のクラスは、私はint型とゼロに初期化変数を宣言します。ほとんどのケースでは、Javaクラスの状態に属しているクラスです。
そこステートリードスレッドセーフのための必要条件であるが、それは十分条件ではない、以下をお読みください。
3.3プライベートクラスのステータス
他の方法でのJavaクラスのステータスThreadLocalの場合、そのような状況は、例えば、スレッドで単離し、互いに干渉しています
public class PrivateStateClass {
private ThreadLocal<Integer> i = new ThreadLocal<>(); public void set(int i) { i.set(i); } public void increment() { Integer value = i.get(); i.set(value + 1); } }
インクリメント()メソッドは、マルチスレッドセーフアクセスすることができる行いながら、上記のクラスは、変数のThreadLocal Iを宣言し、このクラスの状態は、プライベート状態として、種々のスレッドによって単離しました。
3.4株のクラスのステータス
通常のJavaメンバ変数をスレッド、オブジェクトのクラスにアクセスするためのJavaクラスによって提供される複数のスレッドのクラスメソッドによって共有され、クラスのメンバ変数オブジェクトがアクセスを共有することができ、これは、ほとんどの場合のシナリオです。
マルチスレッドの状態を共有し、ひいては2つの場合を議論する状態の定常および可変状態に分けることができ、必ずしも危険ではない、以下を参照されたいです。
3.5不変クラス状態(一定の状態)
次のJavaクラスは、この変数は初期化後に変更されない一定の対象であることを示しているfinalとして宣言されている整数PI変数、そこにあります。
public class FinalStateClass {
private final Integer PI = 3.14; public double calculate(double radius) { return PI*radius*radius; } }
計算上のマルチスレッドアクセス()メソッドは、スレッドセーフです。
最終宣言は、変数は、マルチスレッドの一定の状態が読み取り専用ある程度を達成するために訪問中の状態を変更することはできませんとなりますので、スレッドセーフです。
3.6変数の状態クラス
不適切なコード設計ならば、変数の共有状態の場合は、ときマルチスレッドアクセス、共同作業や同期の問題があるでしょう、不安を通すために非常になりやすいです。
マルチスレッドを設計する際に、可変共有状態の訪問のために、それが重要な考慮事項です。通常、次の二つの方法により、スレッドの安全性を実現するために、
- 非ブロッキング設計(スレッドセーフなアルゴリズムによってを達成するために、マルチスレッドの並列実行)
- 設計ブロックする(ロックを、そのためには、シリアル実行を実現するマルチスレッド)
ここではこれらの2つの方法の簡単な比較があり、
ノンブロッキング設計 | ブロッキングデザイン | |
---|---|---|
マルチスレッド実行 | パラレル実行 | シリアル実行 |
セキュリティの実装 | アルゴリズム設計により、 | ロックによって |
スループットパフォーマンス | 高いです | 低いです |
利点 | デッドロック、スレッドが保留中のブロックされないことはありません | 制御ロックスレッドのスケジューリングによって達成することができます |
短所 | アルゴリズムの複雑さは、競争力の高い状況では、スループットはロックよりも低くなります | ハングスレッドとコンテキストの切り替え、デッドロック |
より詳細な議論は、以下を参照してください。
3.7ノンブロッキング設計
自己増力原子アルゴリズム変数をノンブロッキング次のJavaクラスのAtomicInteger。
public class AtomicStateClass {
private AtomicInteger i = new AtomicInteger(0); public void increment() { i.incrementAndGet(); } }
私たちは、増分()メソッドは、すべてのロックを追加しませんが、それは安全なインクリメント演算子のマルチスレッド化を達成することができます見ることができます。原理はのAtomicInteger CASアルゴリズム、すなわちのcompareAndSet()メソッド、第1の設定値に変化は、ほとんどの場合、再試行し、変更する最初の試みで値を設定するために、被写体が存在しない場合、変数は、変更するかどうかを確認しています彼らは成功するでしょう。
複数のそのような非ブロックスタックとして、アルゴリズム設計を非ブロック、非ブロックリストを挿入し、参照ここ。
3.8デザインをブロック
、スレッドがアクセスを他の人を待って中断されたスレッドによってのみ現在の状態を訪問するようにロックが解除されるまで待機し、ロックアクセスして、スレッドクラスの状態を制御するための手段をブロックする、ロックを競合するすべての待機中のスレッドは、次の訪問を取得します右。
それぞれの同期、シリアル実行コード命令、間のスレッドが競合状態を回避するように、デザインをロックします。しかし同時に、それはまた、デッドロックの問題をもたらします。他の当事者がそれぞれ他の二つのスレッド間でリソースまたはロックを保持する必要がある場合は、デッドロック状態になります。
JVMは、より多くの、表示する監視ツールを提供することであり、メカニズムにデッドロックを解決するためのより良い方法を提供していません。デッドロックのために、最終的な解決策は、コードを実装するために、開発者に頼ることで、より多くのリソースを追加し、ロックの衝突を減らす、整然としたは随時ロックと解除を保持して、デッドロックを回避するための効果的なプログラムです。
3.8.1リソースのデッドロック(リソースDEADLOCK)
リソースデッドロックは、デッドロック広義の定義である簡単な例で、プリンタと印刷ジョブはファイルオブジェクトを取得する必要があり、スレッドは、プリンタ、および他のリリースリソースは利用できませんファイルオブジェクトを取得するために、他のスレッドを取得する場合、リソースのデッドロックが発生します。
そのようなデッドロック効果的なプログラムを解決するために、より多くのリソースを追加します。
3.8.2ロック・シーケンス・デッドロック(LOCK-オーダーDEADLOCK)
以下は、ロックデッドロックのデモコード配列であり、
public class LockOrderingDeadLock {
public void transferMoney(Account from, Account to, Integer amount) { synchronized (from) { synchronized (to) { from.debit(amount); to.credit(amount); } } } }
2つのスレッドが同時に起動した場合は、別途以下の2つの操作を実行し、
- スレッド1:transferMoney(accountA、accountB、100)
- スレッド2:transferMoney(accountB、accountA、100)
同時にホールドaccountAオブジェクトのロックでスレッド1は、スレッド2もaccountBロックを保持しているので、状態をデッドロックする可能性があります。以下では、JConsoleので観測されたデッドロック状態検査transferMoneyの方法であり、
図1:プール-1-スレッド-5は120f74e3ロック@アカウントを保持し、ロック待機アカウントの@ 3e9369b9
図2:プール-1-スレッド-8は120f74e3 @アカウント@の3e9369b9ロック、ロック待機アカウントを保持しています
一つの解決策は、2つのオブジェクトのためにかかわらず、スレッド、ソートされなければならないロックの、(ソートアルゴリズムが安定して秩序でなければならない)ソートするために、AとBをロックシーケンシャルロックホールドを達成することです、得るために、このように互いのロックを保持する必要性を回避することができます。
3.8.3開いた状態
状態開示されたクラスを指すメンバ変数が開示され、ある程度のオブジェクト指向設計のデータカプセル化を損なわ。状態が開示された後も、クラスの設計のベストを遮断する方法は、それは同時実行セーフの及ばないだろう。
见下面的例子,类中定义了一个personList的对象,方法insert()和iterate()通过synchronized进行了阻塞加锁,其只能运行一个线程进入类方法执行操作。
public class PublicStateClass {
public ArrayList<String> personList = new ArrayList<>(); public synchronized void insert(String person) { personList.add(person); } public synchronized void iterate() { Integer size = personList.size(); for (int i = 0; i < size; i++) { System.out.println(personList.get(i)); } } }
但多线程访问insert()和iterate()方法时,并不一定线程安全,主要原因是personList被声明了公开对象,使得类之外的线程可以轻易地访问到personList变量,从而导致personList的状态不一致,在iterate整个person列表时,可能列表中的对象已被删除。
这是类状态公开导致的线程安全问题,究其原因,还要归结于没有做好类的面对对象设计,对外部没有隐藏好数据。
下面的getList方法返回也会导致同样的问题,
public class PublicStateClass {
private ArrayList<String> personList = new ArrayList<>(); public List getList() { return personList; } }
对于这样的问题,推荐的做法是,成员变量声明为私有,在执行读操作时,对外克隆一份数据副本,从而保证类内部数据对象不被泄露,
public class PublicStateClass {
private ArrayList<String> personList = new ArrayList<>(); public List getList() { return (List) personList.clone(); } }
4. 类的静态状态
类的静态状态是指类中被static声明的成员变量,这个状态会在类初次加载时初始化,被所有的类对象所共享。Java程序员对这个static关键字应该不会陌生,其使用的场景还是非常广泛,比如一些常量数据,由于没有必要在每个Java对象中存储一份,为了节省内存空间,很多时候声明为static变量。
但static变量并发不安全,从面向对象设计来说,一旦变量声明为静态,则作用空间扩大到整个类域,若被声明为公共变量,则成为全局性的变量,static的变量声明大大破坏了类的状态封装。
为了使静态变量变得多线程并发安全,final声明是它的“咖啡伴侣”。在阿里巴巴的编码规范中,其中一条是,若是static成员变量,必须考虑是否为final。
5. 类外部状态和多线程安全并发
上文在讲并发设计时,都是针对类内部状态,即类内部成员变量被声明为私有,类方法只对类内部变量进行操作,这是一种简化的应用场景,针对的是依据完全面向对象设计的Java类。一种更常见的情况是,类方法需要对外部传入的对象进行操作。这个时候,类的并发设计则和外部状态息息相关。
例如,
public class StatelessClass {
public void iterate(List<Person> personList) { Integer size = personList.size(); for (int i = 0; i < size; i++) { System.out.println(personList.get(i)); } } }
上面的类是一个无状态类,里面没有任何声明的变量。但是iterate方法接受一个personList的列表对象,由外部传入,personList是一个外部状态。
外部状态类似上文中内部状态公开,无论在类方法上做如何的参数定义(使用ThreadLocal/final进行声明定义),做如何并发安全措施(加锁,使用非阻塞设计),类方法其对状态的操作都是不安全的。外部状态的安全性取决于外部的并发设计。
一个简单的处理方法,在调用类方法的地方,传入一个外部状态的副本,隔离内外部数据的关联性。
6. 小结
类状态的并发,本质上是内存共享数据对象的多线程访问问题。只有对代码中各个Java对象变量的状态特性掌握透彻,写起并发代码时将事倍功半。
下面的类中,整个hasPosition()方法被synchronized修饰,
public class UserLocator {
private final Map<String, String> userLocations = new HashMap<>(); public synchronized boolean hasPositioned(String name, String position) { String key = String.format("%s.location", name); String location = userLocations.get(key); return location != null && position.equals(location); } }
但仔细查看可以知道外部变量name和position、内部变量key和location都是并发安全,只有userLocations这个变量存在并发风险,需要加锁保护。因此,将上面的方法进行如下调整,将减少锁的粒度,有效提高并发效率。
public class UserLocator {
private final Map<String, String> userLocations = new HashMap<>(); public boolean hasPositioned(String name, String position) { String key = String.format("%s.location", name); String location; synchronized (this) { location = userLocations.get(key); } return location != null && position.equals(location); } }
由此可见,了解类中各个变量特性对写好并发安全代码的重要性。在这个基础上,优化锁的作用范围,减少锁的粒度,实现锁分段,都可以做到信手拈来,游刃有余。
クラスの状態について、彼はそんなに、そして最後に要約の全文に言った:設計されたオブジェクト指向のクラスはプライベートプライベートしようとする、厳密に変数の範囲へのアクセスを制御する、良いデータ、良好な制御状態のクラスを非表示にする、ことができ、最終的な可能な限り最終、これらは、並行コードの堅牢性を向上させるために役立ちます。
7.デモ・コード
次のコードリポジトリ内のすべてのデモコード、
8.参照文献
- 9787111370048: "戦闘でのJava並行処理" [US]ブライアン・ゲッツ待っているが、トンYunlanは、ISBNを翻訳しました。
- IBM developerWorksの:非ブロックアルゴリズムの紹介