您好正在阅读关于字符串是线程安全的,因为它是不可变的。
例如我这样做:
String a = "test";
一个线程使用这个变量。但是另一个线程仍然可以使用这个变量并改变它:
a = a + "something";
那么它会改变还是不改变?
如果它是易变的,我会得到它,它一次只能由一个线程使用。但是不变性并不能保证我这一点!?
您好正在阅读关于字符串是线程安全的,因为它是不可变的。
例如我这样做:
String a = "test";
一个线程使用这个变量。但是另一个线程仍然可以使用这个变量并改变它:
a = a + "something";
那么它会改变还是不改变?
如果它是易变的,我会得到它,它一次只能由一个线程使用。但是不变性并不能保证我这一点!?
您没有更改 指向的对象a
,而是更改 wherea
指向的对象:
String a = "test";
这里a
指向一个字符串"test"
a = a + "something";
这里创建了一个新字符串,作为"test"
and连接的结果"something"
,"testsomething"
其中 a 指向。这是一个不同的例子。
所以不存在线程安全问题,因为两个线程都有自己的a
引用同一个"test"
字符串对象,但是一旦其中一个线程将字符串修改为引用该"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
。事实上,这通常是无论如何都会发生的,因为您通常通过参数将字符串传递给线程。
所以两个线程都持有对原始字符串的引用,这里是"test"
。如果线程 1 现在修改a
它只修改这个引用。线程 2 仍然保留完整的引用,"test"
因为字符串本身(而不是引用)是不可变的。
字符串对象是线程安全的。如果你String a
是一个局部变量,那么这段代码仍然是线程安全的。如果它是您班级的一个字段,那么您有责任保证其线程安全。String 的线程安全不会神奇地使您自己的代码线程安全。你应该照顾它。
您可以使字段可变,然后在线程之间获得可见性。因此,任何线程都会看到您的字段的最新值。但是你不会以这种方式获得原子性。想象以下。让a = "test"
. 线程 1 更新 a,线程 2 更新 a。他们都看到当前值是"test"
。他们读取它,通过连接创建新字符串并更新a
. 那将是什么价值?这是未知的。可能是"testsomethingsomething"
线程一个接一个地严格执行它们的操作。但它可以只是"testsomething"
。例如:
"test"
自a
"test"
从a
a
为"testsomething"
a
(记住,它和以前一样a
)"test"
"testsomething"
瞧,您丢失了对您的领域的更新。为避免此类问题,您应该使用单个锁定对象上的同步来保护对您的字段的所有访问和修改。