こんにちは、文字列は不変であるためスレッドセーフであると読んでいました。
たとえば、私は:
String a = "test";
1 つのスレッドがこの変数を使用します。ただし、別のスレッドでもこの変数を使用して変更することができます。
a = a + "something";
それで、それは変わるでしょうか?
揮発性であれば、一度に1つのスレッドでしか使用できないことがわかります。しかし、不変性は私にこれを保証しません!?
こんにちは、文字列は不変であるためスレッドセーフであると読んでいました。
たとえば、私は:
String a = "test";
1 つのスレッドがこの変数を使用します。ただし、別のスレッドでもこの変数を使用して変更することができます。
a = a + "something";
それで、それは変わるでしょうか?
揮発性であれば、一度に1つのスレッドでしか使用できないことがわかります。しかし、不変性は私にこれを保証しません!?
が指すオブジェクトを変更しているのではなく、次を指してa
いる場所a
:
String a = "test";
ここa
で文字列を指します"test"
a = a + "something";
ここで、 a が指している"test"
と"something"
を連結した結果として、新しい文字列が作成されます。"testsomething"
別の例です。
a
したがって、両方のスレッドが同じ文字列オブジェクトを参照する独自のスレッドを持つため、スレッド セーフの問題はありませんが"test"
、これらのスレッドの 1 つがオブジェクトを参照するように文字列を変更すると、もう 1 つの"testsomething"
スレッドは元のオブジェクトを引き続き参照し"test"
ます。
文字列自体は変更されていません。参照は変更されていません。参照を最終的にする必要があるようです。不変性は、参照が変更できないことではなく、オブジェクトが変更されないことを保証します。次のようにマークするだけです。
final String a = "test";
ここで大混乱…
一部のクラスのスレッドセーフとは、そのインスタンスを同時に使用しても内部構造が破壊されないことを意味します。
私たちの場合、最終的に「testsomething」を取得することは単なる保証ですが、「tsomethingest」、「tesomethingst」、「tseosmtething」、「somethingtest」のような混乱ではありません。
ここに「手っ取り早い」図があります。
public class Test2 {
private volatile String tstStr = "";
Test2(){
}
void impl(int par){
Thread wrk = new Thread(new MyRun(par));
wrk.start();
}
static public void main(String[] args) throws Exception {
Test2 tst2 = new Test2();
long startTime = System.currentTimeMillis();
Thread wrk;
for (int i = 0; i < 9; i=i+1) {
tst2.impl(i);
}
long endTime = System.currentTimeMillis();
System.out.println("The process took " + (endTime - startTime) + " milliseconds");
}
class MyRun implements Runnable {
int no;
MyRun(int var){
no = var;
}
public void run(){
tstStr = tstStr + " " + no;
for (int i = 0; i < 3; i=i+1) {
System.out.println("Message from "+no+", tested string ="+tstStr);
}
}
}
}
出力:
Message from 1, tested string = 0
Message from 1, tested string = 0 2 3
Message from 1, tested string = 0 2 3
Message from 4, tested string = 0 2 3 4
Message from 4, tested string = 0 2 3 4
Message from 0, tested string = 0 2
Message from 8, tested string = 0 2 3 4 7 8
Message from 5, tested string = 0 2 3 4 7 8 5
Message from 0, tested string = 0 2 3 4 7 8 5
Message from 0, tested string = 0 2 3 4 7 8 5
The process took 0 milliseconds
Message from 7, tested string = 0 2 3 4 7
Message from 7, tested string = 0 2 3 4 7 8 5 6
Message from 4, tested string = 0 2 3 4
Message from 3, tested string = 0 2 3
Message from 2, tested string = 0 2
Message from 3, tested string = 0 2 3 4 7 8 5 6
Message from 7, tested string = 0 2 3 4 7 8 5 6
Message from 6, tested string = 0 2 3 4 7 8 5 6
Message from 5, tested string = 0 2 3 4 7 8 5
Message from 8, tested string = 0 2 3 4 7 8 5
Message from 5, tested string = 0 2 3 4 7 8 5 6
Message from 6, tested string = 0 2 3 4 7 8 5 6
Message from 3, tested string = 0 2 3 4 7 8 5 6
Message from 2, tested string = 0 2 3 4 7 8 5 6
Message from 6, tested string = 0 2 3 4 7 8 5 6
Message from 8, tested string = 0 2 3 4 7 8 5 6
Message from 2, tested string = 0 2 3 4 7 8 5 6
各スレッドに参照をコピーさせることで、コードを簡単にスレッドセーフにすることができますa
。実際、通常はパラメーターを介して文字列をスレッドに渡すため、通常はとにかくこれが発生します。
したがって、両方のスレッドが元の文字列 here への参照を保持します"test"
。スレッド 1 が変更すると、この参照a
のみが変更されます。文字列自体(参照ではなく) が不変であるため、スレッド 2 は への無傷の参照を保持します。"test"
Strings オブジェクトはスレッドセーフです。yourString a
がローカル変数の場合、このコードは依然としてスレッドセーフです。それがあなたのクラスのフィールドである場合、そのスレッドセーフを保証するのはあなたの責任です。String のスレッド セーフは、独自のコードを魔法のようにスレッド セーフにするわけではありません。あなたはそれを気にする必要があります。
フィールドを揮発性にすると、スレッド間の可視性が得られます。そのため、どのスレッドにもフィールドの最新の値が表示されます。しかし、この方法では原子性は得られません。以下を想像してください。しましょうa = "test"
。スレッド 1 は a を更新し、スレッド 2 は a を更新します。どちらも現在の値である を参照しています"test"
。彼らはそれを読み取り、連結によって新しい文字列を作成し、 の値を更新しますa
。そして、その値はどうなりますか?不明です。"testsomethingsomething"
スレッドが厳密に操作を次々と実行する場合に発生する可能性があります。しかし、それはただのことができます"testsomething"
。例えば:
"test"
スレッド 1からの読み取りa
"test"
から読み取るa
a
の更新"testsomething"
a
たことを思い出してください)。a
"test"
"testsomething"
ほら、フィールドへの更新が失われました。この種の問題を回避するには、フィールドへのすべてのアクセスと変更を単一のロック オブジェクトの同期で保護する必要があります。