Java 多线程:线程安全数据结构与同步方法
Java multi-threading: thread safe data structure vs. synchronized method
我有一个 class TestLogger
有一个 void 方法 log(String s)
,它可以被多个线程访问。这是我的代码
public class TestLogger {
private static final StringBuffer buffer = new StringBuffer();
public static void log(String s) {
buffer.append(s);
}
}
这里我不确定我是否使用了线程安全classStringBuffer
,我是否还需要在方法log(String)
上放置synchronized关键字以确保方法的线程安全?这个方法怎么样
public static void log(String s, int type) {
if (type == 0)
buffer.append(s);
if (type == 1)
buffer.append("SOME HEADER " + s);
}
这里的类型没有在方法日志中修改。我需要使用同步关键字吗?
在Java中,既有synchronized关键字,也有thread safe classes可以提供线程安全。我不确定何时使用一个和另一个?
由于 StringBuffer 是线程安全的,因此您不需要围绕它设置另一个同步点(这是 synchronized 会做的)。
由于此方法的任一实现最多访问 buffer
一次,从技术上讲您不需要同步该方法,但这是一个非常糟糕的做法。
首先,它非常脆弱。一些粗心的开发人员看了这个方法并决定 "optimize" 它(为了更好的 Java 代码!)就足以让你失去 StringBuffer
的同步:
public static void log(String s, int type) {
if (type == 0)
buffer.append(s);
if (type == 1)
buffer.append("SOME HEADER ").optimize(s);
}
此代码段显示了对 append
的两次单独调用,因此如果您同时调用 log('A', 1)
和 log('B',1)
,您的结果缓冲区很可能是 "SOME HEADER SOME HEADER AB"
.
其次,即使您没有在技术上破坏同步,仅依靠 StringBuffer
来满足您的同步需求,您也可能会发现轻微的行为异常。例如,考虑将来请求也记录消息的时间:
public static void log(String s) {
Date d = new Date();
buffer.append(d.toString() + " " + s);
}
如果您对该方法有相当多的并发调用,您可能会遇到线程 A 创建新的 Date
实例,上下文切换到另一个线程完成整个方法,然后返回到线程 A。这将使您的日志看起来好像在时光倒流,这绝对不是您想要的。
第三,也是最重要的,将方法定义为 synchronized
具有声明性价值。它传达了此方法在多线程上下文中的行为方式以及您期望两个并发调用如何相互反应的信息。在设计供他人使用的 public 效用函数中,这是最重要的。
StringBuilder
在 java 中不是线程安全的。所以你可以使用线程安全的StringBuffer
。
synchronized
关键字有两种不同的使用方式。
同步方法:它使方法线程安全。
public synchronized static void log (String log) {
buffer.append(log);
}
同步语句:它使指定对象线程安全。
public static void log(String log) {
synchronized (buffer) {
buffer.append(log);
}
}
And how about this method...
你的两个方法同样是线程安全的(或者不是!,见下行)。无论调用哪个方法,都会发生同样的事情:恰好一个字符串将被添加到共享缓冲区。
使用线程安全对象并不能使程序成为线程安全的。 "thread-safe" 的含义由您决定。
当有人告诉你某些 class 是线程安全的时,他们承诺的是,多个线程不可能调用 class 的方法可以使它们中的任何一个以 "wrong" 方式运行。
"wrong" 是什么意思?这要看情况。当然,任何不同意 class 文档的行为都是错误的。通常,任何不符合合理程序员预期的行为也可能被称为错误。
在 StringBuffer 的例子中,"thread-safe" 的意思是:这意味着:
- 您不会在缓冲区中找到您的程序未放入缓冲区的任何内容。
- 您不会从缓冲区中找到任何丢失的东西,您的程序确实将其放入其中。
- 你不会从两个不同的字符串中找到相互交错的字符,最后,
- 如果你能证明字符串 A 在字符串 B 之前被附加,那么字符串 A 将在字符串 B 之前出现在输出中。
您的示例方法调用是线程安全的,因为每个方法调用都只调用一个线程安全的共享对象。
如果您的示例有 多个 共享对象,那么对象的各个线程安全性可能 not 加起来到线程-整个算法的安全性。
我有一个 class TestLogger
有一个 void 方法 log(String s)
,它可以被多个线程访问。这是我的代码
public class TestLogger {
private static final StringBuffer buffer = new StringBuffer();
public static void log(String s) {
buffer.append(s);
}
}
这里我不确定我是否使用了线程安全classStringBuffer
,我是否还需要在方法log(String)
上放置synchronized关键字以确保方法的线程安全?这个方法怎么样
public static void log(String s, int type) {
if (type == 0)
buffer.append(s);
if (type == 1)
buffer.append("SOME HEADER " + s);
}
这里的类型没有在方法日志中修改。我需要使用同步关键字吗?
在Java中,既有synchronized关键字,也有thread safe classes可以提供线程安全。我不确定何时使用一个和另一个?
由于 StringBuffer 是线程安全的,因此您不需要围绕它设置另一个同步点(这是 synchronized 会做的)。
由于此方法的任一实现最多访问 buffer
一次,从技术上讲您不需要同步该方法,但这是一个非常糟糕的做法。
首先,它非常脆弱。一些粗心的开发人员看了这个方法并决定 "optimize" 它(为了更好的 Java 代码!)就足以让你失去 StringBuffer
的同步:
public static void log(String s, int type) {
if (type == 0)
buffer.append(s);
if (type == 1)
buffer.append("SOME HEADER ").optimize(s);
}
此代码段显示了对 append
的两次单独调用,因此如果您同时调用 log('A', 1)
和 log('B',1)
,您的结果缓冲区很可能是 "SOME HEADER SOME HEADER AB"
.
其次,即使您没有在技术上破坏同步,仅依靠 StringBuffer
来满足您的同步需求,您也可能会发现轻微的行为异常。例如,考虑将来请求也记录消息的时间:
public static void log(String s) {
Date d = new Date();
buffer.append(d.toString() + " " + s);
}
如果您对该方法有相当多的并发调用,您可能会遇到线程 A 创建新的 Date
实例,上下文切换到另一个线程完成整个方法,然后返回到线程 A。这将使您的日志看起来好像在时光倒流,这绝对不是您想要的。
第三,也是最重要的,将方法定义为 synchronized
具有声明性价值。它传达了此方法在多线程上下文中的行为方式以及您期望两个并发调用如何相互反应的信息。在设计供他人使用的 public 效用函数中,这是最重要的。
StringBuilder
在 java 中不是线程安全的。所以你可以使用线程安全的StringBuffer
。
synchronized
关键字有两种不同的使用方式。
同步方法:它使方法线程安全。
public synchronized static void log (String log) {
buffer.append(log);
}
同步语句:它使指定对象线程安全。
public static void log(String log) {
synchronized (buffer) {
buffer.append(log);
}
}
And how about this method...
你的两个方法同样是线程安全的(或者不是!,见下行)。无论调用哪个方法,都会发生同样的事情:恰好一个字符串将被添加到共享缓冲区。
使用线程安全对象并不能使程序成为线程安全的。 "thread-safe" 的含义由您决定。
当有人告诉你某些 class 是线程安全的时,他们承诺的是,多个线程不可能调用 class 的方法可以使它们中的任何一个以 "wrong" 方式运行。
"wrong" 是什么意思?这要看情况。当然,任何不同意 class 文档的行为都是错误的。通常,任何不符合合理程序员预期的行为也可能被称为错误。
在 StringBuffer 的例子中,"thread-safe" 的意思是:这意味着:
- 您不会在缓冲区中找到您的程序未放入缓冲区的任何内容。
- 您不会从缓冲区中找到任何丢失的东西,您的程序确实将其放入其中。
- 你不会从两个不同的字符串中找到相互交错的字符,最后,
- 如果你能证明字符串 A 在字符串 B 之前被附加,那么字符串 A 将在字符串 B 之前出现在输出中。
您的示例方法调用是线程安全的,因为每个方法调用都只调用一个线程安全的共享对象。
如果您的示例有 多个 共享对象,那么对象的各个线程安全性可能 not 加起来到线程-整个算法的安全性。