简体   繁体   English

Java多线程:线程安全的数据结构与同步方法

[英]Java multi-threading: thread safe data structure vs. synchronized method

I have a class TestLogger that has a void method log(String s) , which may be accessed by multiple threads. 我有一个TestLogger类, TestLogger具有一个void方法log(String s) ,该方法可以由多个线程访问。 Here is my code 这是我的代码

public class TestLogger {
    private static final StringBuffer buffer = new StringBuffer();

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

I am not sure here if I used the thread safe class StringBuffer , do I still need to put synchronized keyword on the method log(String) to ensure thread safety of the method? 我不确定在这里是否使用了线程安全类StringBuffer ,是否仍然需要在log(String)方法上放置synced关键字以确保方法的线程安全? And how about this method 那这种方法怎么样

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

here type is not modified in the method log. 此处的类型在方法日志中未修改。 Do I need to use synchronized keyword? 我需要使用同步关键字吗?

In Java, there are both synchronized keyword and thread safe classes that can provide thread safety. 在Java中,既有synced关键字又有线程安全类,它们可以提供线程安全。 I am not sure when to use one and the other? 我不确定何时使用另一个?

Since either implementation of this method accesses buffer at most once, you don't technically need to synchronize the method, but it's a really bad practice. 由于此方法的任一实现最多只能访问buffer一次,因此从技术上讲,您不需要同步该方法,但这是一个非常糟糕的做法。

First, it's extremely fragile. 首先,它非常脆弱。 It's enough that some careless developer takes a look at this method and decides to "optimize" it (to better Java code!) for you to lose the StringBuffer 's synchronization: 足够粗心的开发人员研究一下此方法并决定对其进行“优化”(以改进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);
}

This snippet shows two separate calls to append , so if you concurrently call log('A', 1) and log('B',1) , your resulting buffer may very well be "SOME HEADER SOME HEADER AB" . 此代码段显示了两个单独的append调用,因此,如果同时调用log('A', 1)log('B',1) ,则生成的缓冲区很可能是"SOME HEADER SOME HEADER AB"

Second, even if you don't technically break synchronization, by relying solely on the StringBuffer to supply your synchronization needs, you may discover slight behavior oddities. 其次,即使您从技术上讲并未中断同步,通过仅依靠StringBuffer满足您的同步需求,您也可能会发现一些奇怪的行为。 For example, consider a future request to also log the message's time: 例如,考虑将来的请求也要记录消息的时间:

public static void log(String s) {
    Date d = new Date();
    buffer.append(d.toString() + " " + s);
}

If you have a considerable number of concurrent calls to this method, you may experience thread A creating the new Date instance, context switching to another thread that completes the entire method and then returning to thread A. This will make your log appear as though it's moving back in time, which is definitely not what you want. 如果对该方法有大量并发调用,您可能会遇到线程A创建新的Date实例,上下文切换到另一个线程,该线程完成整个方法,然后返回线程A。这将使您的日志看起来像是时光倒流,这绝对不是您想要的。

And third, and most importantly, defining the method as synchronized has a declarative value. 第三,也是最重要的是,将方法定义为synchronized具有声明性的价值。 It conveys the information of how this method behaves in a multithreaded context and how you'd expect two concurrent call to react to each other. 它传达了有关此方法在多线程上下文中的行为以及如何期望两个并发调用相互反应的信息。 In a public utility function designed to be used by others, this is paramount. 在旨在供他人使用的公用事业功能中,这是至关重要的。

And how about this method... 那这种方法呢...

Your two methods are equally thread-safe (or not!, see below the line). 您的两个方法同样是线程安全的(或不安全!,请参见下面的行)。 No matter which method is called, the same thing will happen: Exactly one string will be added to the shared buffer. 无论调用哪种方法,都会发生相同的事情:恰好一个字符串将添加到共享缓冲区。


Using thread-safe objects does not make a program thread-safe. 使用线程安全的对象不会使程序成为线程安全的。 It's up to you to decide what "thread-safe" means. 由您决定“线程安全”的含义。

When somebody tells you that some class is thread-safe, what they're promising is, that there's no way that multiple threads calling the class's methods can make any of them behave in "wrong" ways. 当有人告诉您某个是线程安全的时,他们所承诺的是,没有办法让多个调用该类方法的线程使它们中的任何一个以“错误”的方式运行。

And what does "wrong" mean? “错误”是什么意思? Well, it depends. 这要看情况。 Certainly, any behavior that disagrees with the class's documentation would be wrong. 当然,任何不同意该类文档的行为都是错误的。 And often, any behavior that disagrees with what a reasonable programmer would expect could be called wrong too. 通常,任何与合理的程序员所期望的不一致的行为也可能被称为错误。

In the case of StringBuffer, here's what "thread-safe" means: It means, that: 对于StringBuffer,这是“线程安全”的意思:它的意思是:

  • You won't find anything in the buffer that your program didn't put in the buffer. 您不会在程序中没有放入缓冲区的缓冲区中找到任何内容。
  • You won't find anything missing from the buffer that your program did put in there. 您不会在程序确实放在那里的缓冲区中找到任何丢失的东西。
  • You won't find the characters from two different strings interleaved with one another, and finally, 您将找不到两个相互交织的不同字符串中的字符,最后,
  • If you can prove that string A was appended before string B was appended, then string A will appear in the output before string B. 如果您可以证明在添加字符串B之前先添加了字符串A,则字符串A将在输出中出现在字符串B之前。

Your example method calls are thread safe simply by virtue of the fact that each one makes just one call on a thread-safe shared object. 您的示例方法调用是线程安全的,这是因为每个调用仅对线程安全的共享对象进行一次调用。

If your example had several shared objects though, that's when the individual thread-safety of the objects might not add up to thread-safety for the whole algorithm. 如果您的示例中有多个共享对象,则那时对象的单个线程安全性可能不会与整个算法的线程安全性相加。

由于StringBuffer是线程安全的,因此您不需要在其周围有另一个同步点(这就是同步的作用)。

StringBuilder is not thread safe in java. StringBuilder在Java中不是线程安全的。 So you can use StringBuffer which is thread safe. 因此,您可以使用线程安全的StringBuffer

The synchronized keyword can be used in two different ways. 可以以两种不同的方式使用synchronized关键字。

Synchronized Methods: It makes the method thread-safe. 同步方法:使该方法成为线程安全的。

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

Synchronized Statements: It make the specifed object thread-safe. 同步语句:它使指定的对象成为线程安全的。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM