共有データは、同時実行プログラムの最も重要な機能の 1 つです。これは、Thread クラスを継承するオブジェクトであっても、Runnable インターフェイスを実装するオブジェクトであっても、非常に重要な側面です。
Runnable インターフェイスを実装するクラスのオブジェクトを作成し、そのオブジェクトを使用して一連のスレッドを開始すると、それらのスレッドはすべて同じプロパティを共有します。つまり、1 つのスレッドがプロパティを変更すると、残りのすべてのスレッドがその変更の影響を受けます。
場合によっては、同じオブジェクトで開始された他のスレッドと共有するのではなく、スレッド内で単独で使用することを好むことがあります。 Java 同時実行インターフェイスは、スレッドローカル変数と呼ばれる、この要件を満たすための非常に明確なメカニズムを提供します。このメカニズムのパフォーマンスも非常に印象的です。
それを知っています
以下の手順でサンプルプログラムを完成させます。
1. まず、上記の問題を抱えたプログラムを実装します。 UnsafeTask という名前のクラスを作成し、Runnable インターフェイスを実装します。クラス内で java.util.Date 型のプライベート プロパティを宣言します。コードは次のとおりです。
次のようにコードをコピーします。
public class UnsafeTask は Runnable {を実装します
プライベート日付 startDate;
2. UnsafeTask の run() メソッドを実装します。このメソッドは、startDate 属性をインスタンス化し、その値をコンソールに出力します。ランダムな期間スリープしてから、startDate 属性の値を再度コンソールに出力します。コードは次のとおりです。
次のようにコードをコピーします。
@オーバーライド
public void run() {
startDate = 新しい日付();
System.out.printf("開始スレッド: %s : %s/n",
Thread.currentThread().getId(), startDate);
試す {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("スレッド終了: %s : %s/n",
Thread.currentThread().getId(), startDate);
}
3. 問題のあるプログラムのメインクラスを実装します。 main() メソッド UnsafeMain を使用してクラスを作成します。 main() メソッドで UnsafeTask オブジェクトを作成し、このオブジェクトを使用して 10 個の Thread オブジェクトを作成し、10 個のスレッドを開始します。各スレッドの途中で 2 秒間スリープします。コードは次のとおりです。
次のようにコードをコピーします。
パブリック クラス UnsafeMain {
public static void main(String[] args) {
UnsafeTask タスク = new UnsafeTask();
for (int i = 0; i < 10; i++) {
スレッド thread = 新しいスレッド(タスク);
thread.start();
試す {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4. 上記のロジックから、各スレッドの起動時間は異なります。ただし、以下の出力ログによれば、同一の時刻値が多数存在します。次のように:
次のようにコードをコピーします。
開始スレッド: 9 : 2013 年 9 月 29 日日曜日 23:31:08 CST
開始スレッド: 10 : 2013 年 9 月 29 日日曜日 23:31:10 CST
開始スレッド: 11 : 2013 年 9 月 29 日日曜日 23:31:12 CST
開始スレッド: 12 : 2013 年 9 月 29 日日曜日 23:31:14 CST
終了したスレッド: 9 : Sun Sep 29 23:31:14 CST 2013
開始スレッド: 13 : Sun Sep 29 23:31:16 CST 2013
終了したスレッド: 10 : Sun Sep 29 23:31:16 CST 2013
開始スレッド: 14 : Sun Sep 29 23:31:18 CST 2013
終了したスレッド: 11 : Sun Sep 29 23:31:18 CST 2013
開始スレッド: 15 : Sun Sep 29 23:31:20 CST 2013
終了したスレッド: 12: Sun Sep 29 23:31:20 CST 2013
開始スレッド: 16 : Sun Sep 29 23:31:22 CST 2013
開始スレッド: 17: 2013 年 9 月 29 日日曜日 23:31:24 CST
終了したスレッド: 17 : Sun Sep 29 23:31:24 CST 2013
終了したスレッド: 15 : Sun Sep 29 23:31:24 CST 2013
終了したスレッド: 13 : Sun Sep 29 23:31:24 CST 2013
開始スレッド: 18 : Sun Sep 29 23:31:26 CST 2013
終了したスレッド: 14 : Sun Sep 29 23:31:26 CST 2013
終了したスレッド: 18 : Sun Sep 29 23:31:26 CST 2013
終了したスレッド: 16 : Sun Sep 29 23:31:26 CST 2013
5. 上に示したように、スレッドローカル変数メカニズムを使用してこの問題を解決します。
6. SafeTask という名前のクラスを作成し、Runnable インターフェイスを実装します。コードは次のとおりです。
次のようにコードをコピーします。
パブリック クラス SafeTask は Runnable {を実装します
7. ThreadLocal<Date> 型のオブジェクトを宣言します。オブジェクトがインスタンス化されると、initialValue() メソッドがオーバーライドされ、このメソッドで実際の日付値が返されます。コードは次のとおりです。
次のようにコードをコピーします。
private static ThreadLocal<Date> startDate = new
ThreadLocal<Date>() {
@オーバーライド
protected Date 初期値() {
新しい Date() を返します。
}
};
8. SafeTask クラスの run() メソッドを実装します。このメソッドは、startDate 属性のメソッドがわずかに調整されていることを除いて、UnsafeTask の run() メソッドと同じです。コードは次のとおりです。
次のようにコードをコピーします。
@オーバーライド
public void run() {
System.out.printf("開始スレッド: %s : %s/n",
Thread.currentThread().getId()、startDate.get());
試す {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("スレッド終了: %s : %s/n",
Thread.currentThread().getId()、startDate.get());
}
9. この安全な例のメイン クラスは、UnsafeTask を SafeTask に変更する必要があることを除いて、基本的に非安全なプログラムのメイン クラスと同じです。具体的なコードは次のとおりです。
次のようにコードをコピーします。
パブリック クラス SafeMain {
public static void main(String[] args) {
SafeTask タスク = new SafeTask();
for (int i = 0; i < 10; i++) {
スレッド thread = 新しいスレッド(タスク);
thread.start();
試す {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
10. プログラムを実行し、2 つの入力間の違いを分析します。
クラスの名前を統一するために、この記事のメインクラスの名前は原文とは少し異なります。また、元のプログラムとテキストの説明は矛盾しています。事務的なミスに違いない。
理由を知っています
以下はセキュリティ例の実行結果です。結果から、各スレッドがそれぞれのスレッドに属する startDate 属性値を持っていることが簡単にわかります。プログラムの入力は次のとおりです。
次のようにコードをコピーします。
開始スレッド: 9 : Sun Sep 29 23:52:17 CST 2013
開始スレッド: 10 : 2013 年 9 月 29 日日曜日 23:52:19 CST
開始スレッド: 11 : Sun Sep 29 23:52:21 CST 2013
終了したスレッド: 10: Sun Sep 29 23:52:19 CST 2013
開始スレッド: 12 : Sun Sep 29 23:52:23 CST 2013
終了したスレッド: 11 : Sun Sep 29 23:52:21 CST 2013
開始スレッド: 13 : Sun Sep 29 23:52:25 CST 2013
終了したスレッド: 9 : Sun Sep 29 23:52:17 CST 2013
開始スレッド: 14 : Sun Sep 29 23:52:27 CST 2013
開始スレッド: 15 : Sun Sep 29 23:52:29 CST 2013
終了したスレッド: 13 : Sun Sep 29 23:52:25 CST 2013
開始スレッド: 16 : Sun Sep 29 23:52:31 CST 2013
終了したスレッド: 14 : Sun Sep 29 23:52:27 CST 2013
開始スレッド: 17: 2013 年 9 月 29 日日曜日 23:52:33 CST
終了したスレッド: 12: Sun Sep 29 23:52:23 CST 2013
終了したスレッド: 16 : Sun Sep 29 23:52:31 CST 2013
終了したスレッド: 15 : Sun Sep 29 23:52:29 CST 2013
開始スレッド: 18 : Sun Sep 29 23:52:35 CST 2013
終了したスレッド: 17 : Sun Sep 29 23:52:33 CST 2013
スレッド終了: 18 : Sun Sep 29 23:52:35 CST 2013
スレッド ローカル変数には、各スレッドのプロパティのコピーが保存されます。 ThreadLocal の get() メソッドを使用して変数の値を取得し、set() メソッドを使用して変数の値を設定できます。スレッドローカル変数に初めてアクセスし、その変数にまだ値が割り当てられていない場合は、initialValue() メソッドが呼び出され、各スレッドの値が初期化されます。
終わりのない
ThreadLocal クラスは、このメソッドを呼び出すスレッドに格納されているローカル変数値を削除するための、remove() メソッドも提供します。
さらに、Java 同時実行 API は InheritableThreadLocal クラスも提供します。これにより、子スレッドはすべての継承可能なスレッド ローカル変数の初期値を受け取り、親スレッドが所有する値を取得できます。スレッド A にスレッド ローカル変数がある場合、スレッド A がスレッド B を作成すると、スレッド B はスレッド A と同じスレッド ローカル変数を持つことになります。 childValue() をオーバーライドして、子スレッドのスレッド ローカル変数を初期化することもできます。このメソッドは、親スレッドからパラメータとして渡されたスレッドローカル変数の値を受け入れます。
ドクトリンを使用する
この記事は「Java 7 Concurrency Cookbook」(D Gua Ge が「Java7 Concurrency Example Collection」として盗用しました) を翻訳したものであり、学習教材としてのみ使用されます。許可なく商業目的で使用することはできません。
小さな成功
以下は、このセクションの例に含まれるすべてのコードの完全版です。
UnsafeTask クラスの完全なコード:
次のようにコードをコピーします。
パッケージ com.diguage.books.concurrencycookbook.chapter1.recipe9;
java.util.Dateをインポートします。
java.util.concurrent.TimeUnitをインポートします。
/**
※スレッドの安全性が保証できない例
* 日付: 2013-09-23
* 時間: 23:58
*/
public class UnsafeTask は Runnable {を実装します
プライベート日付 startDate;
@オーバーライド
public void run() {
startDate = 新しい日付();
System.out.printf("開始スレッド: %s : %s/n",
Thread.currentThread().getId(), startDate);
試す {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("スレッド終了: %s : %s/n",
Thread.currentThread().getId(), startDate);
}
}
UnsafeMain クラスの完全なコード:
次のようにコードをコピーします。
パッケージ com.diguage.books.concurrencycookbook.chapter1.recipe9;
java.util.concurrent.TimeUnitをインポートします。
/**
* 安全でないスレッドの例
* 日付: 2013-09-24
* 時間: 00:04
*/
パブリック クラス UnsafeMain {
public static void main(String[] args) {
UnsafeTask タスク = new UnsafeTask();
for (int i = 0; i < 10; i++) {
スレッド thread = 新しいスレッド(タスク);
thread.start();
試す {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
SafeTask クラスの完全なコード:
次のようにコードをコピーします。
パッケージ com.diguage.books.concurrencycookbook.chapter1.recipe9;
java.util.Dateをインポートします。
java.util.concurrent.TimeUnitをインポートします。
/**
* スレッドの安全性を確保するには、スレッド ローカル変数を使用します
* 日付: 2013-09-29
* 時間: 23:34
*/
パブリック クラス SafeTask は Runnable {を実装します
private static ThreadLocal<Date> startDate = new
ThreadLocal<Date>() {
@オーバーライド
protected Date 初期値() {
新しい Date() を返します。
}
};
@オーバーライド
public void run() {
System.out.printf("開始スレッド: %s : %s/n",
Thread.currentThread().getId()、startDate.get());
試す {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("スレッド終了: %s : %s/n",
Thread.currentThread().getId()、startDate.get());
}
}
SafeMain クラスの完全なコード:
次のようにコードをコピーします。
パッケージ com.diguage.books.concurrencycookbook.chapter1.recipe9;
java.util.concurrent.TimeUnitをインポートします。
/**
* 安全なスレッドの例
* 日付: 2013-09-24
* 時間: 00:04
*/
パブリック クラス SafeMain {
public static void main(String[] args) {
SafeTask タスク = new SafeTask();
for (int i = 0; i < 10; i++) {
スレッド thread = 新しいスレッド(タスク);
thread.start();
試す {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}