Java多线程:线程安全数据结构vs.同步方法



我有一个类TestLogger,它有一个void方法log(String s),可以由多个线程访问。这是我的代码

public class TestLogger {
    private static final StringBuffer buffer = new StringBuffer();
    public static void log(String s) {
        buffer.append(s);
    }
}

我不确定这里是否使用了线程安全类StringBuffer,我是否还需要在方法log(String)上放置同步关键字以确保方法的线程安全?那么

这个方法呢?
public static void log(String s, int type) {
    if (type == 0)
        buffer.append(s);
    if (type == 1)
        buffer.append("SOME HEADER " + s);
}

此处类型未在方法日志中修改。我需要使用synchronized关键字吗?

在Java中,同步关键字和线程安全类都可以提供线程安全。我不确定何时使用一个和另一个?

这个方法怎么样?

你的两个方法同样是线程安全的(或者不是!)(见行下)。无论调用哪个方法,都会发生同样的事情:只会向共享缓冲区中添加一个字符串。


使用线程安全的对象并不能使程序成为线程安全的。什么是"线程安全"由你来决定。

当有人告诉你某个是线程安全的,他们承诺的是,多个线程调用该类的方法不会使它们中的任何一个以"错误"的方式运行。

"错"是什么意思?嗯,这要看情况。当然,任何与类文档不一致的行为都是错误的。通常,任何与一个理性的程序员所期望的不一致的行为也可以被称为错误。

对于StringBuffer,"线程安全"的含义如下:

  • 你不会在缓冲区中找到任何你的程序没有放入缓冲区的东西。
  • 你不会发现任何缺失从你的程序放入那里的缓冲区。
  • 你不会发现来自两个不同字符串的字符彼此交错,最后,
  • 如果你能证明字符串A是在字符串B之前被附加的,那么字符串A将出现在字符串B之前的输出中。

你的示例方法调用是线程安全的,仅仅是因为每个方法调用只对一个线程安全的共享对象进行一次调用。

如果你的例子有几个共享对象,那么这些对象的单独线程安全性可能不是加起来就是整个算法的线程安全性。

由于该方法的任何实现最多一次访问buffer,因此从技术上讲,您不需要同步该方法,但这是一个非常糟糕的实践。

首先,它非常脆弱。一些粗心的开发人员看了看这个方法并决定"优化"它(为了更好的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具有声明性值。它传达了该方法在多线程上下文中的行为方式以及您期望两个并发调用如何相互响应的信息。在设计供他人使用的公共效用函数中,这是至关重要的。

由于StringBuffer是线程安全的,因此不需要在它周围设置另一个同步点(synchronized就是这样做的)。

StringBuilder在java中不是线程安全的。所以你可以使用StringBuffer,这是线程安全的。

synchronized关键字有两种不同的用法。

Synchronized Methods:它使方法线程安全。

public synchronized static void log (String log) { 
        buffer.append(log);
}

同步语句:它使指定的对象线程安全。

public static void log(String log) {
    synchronized (buffer) {
        buffer.append(log);
    }
}

最新更新