Java中对应于'synchronized'的特性是什么?

50

synchronized 在Java中可以确保访问共享对象时的线程安全性。那么C++中呢?


4
C++ 并未涉及线程相关内容,您需要依赖库的支持。 - David Heffernan
@DavidHeffernan:自从C++11以来,它可以了! - Nawaz
1
@Nawaz 是的,您看到这篇文章的日期了吗? - David Heffernan
4
是的,我看到了“3月25日11点” :P - Nawaz
5个回答

53

在C++中使用以下代码:

#include <mutex>

std::mutex _mutex;

void f()
{
     std::unique_lock<std::mutex> lock(_mutex);
     // access your resource here.
}

9
尽管这个问题已经得到回答,但是我根据这篇文章的思路制作了自己的版本synchronized关键字,只使用标准库(C++11)对象即可。文章链接:this
#include <mutex>
#define synchronized(m) \
    for(std::unique_lock<std::recursive_mutex> lk(m); lk; lk.unlock())

您可以这样测试:
#include <iostream>
#include <iomanip>
#include <mutex>
#include <thread>
#include <vector>

#define synchronized(m) \
    for(std::unique_lock<std::recursive_mutex> lk(m); lk; lk.unlock())

class Test {
    std::recursive_mutex m_mutex;
public:
    void sayHello(int n) {
        synchronized(m_mutex) {
            std::cout << "Hello! My number is: ";
            std::cout << std::setw(2) << n << std::endl;
        }
    }    
};

int main() {
    Test test;
    std::vector<std::thread> threads;
    std::cout << "Test started..." << std::endl;

    for(int i = 0; i < 10; ++i)
        threads.push_back(std::thread([i, &test]() {
            for(int j = 0; j < 10; ++j) {
                test.sayHello((i * 10) + j);
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
        }));    
    for(auto& t : threads) t.join(); 

    std::cout << "Test finished!" << std::endl;
    return 0;
}

这只是 Java 中 synchronized 关键字的一个近似,但它可以工作。如果没有它,前面例子的 sayHello 方法可以实现为接受的答案所述:

void sayHello(unsigned int n) {
    std::unique_lock<std::recursive_mutex> lk(m_mutex);

    std::cout << "Hello! My number is: ";
    std::cout << std::setw(2) << n << std::endl;
}

1
如果将其实现为模板,那就太好了,但是我发现,除了一些众所周知的例外(例如_assert_),使用#define_s实现的代码很难调试。 - gerardw

6

C++03中没有与Java中的synchronized等效的关键字。但是,您可以使用Mutex来确保线程的安全性。


2

0
在C++中,我实现了一个宏,让你可以简单地这样做,以便同步访问一个函数或方法:
// Define a function we want to synchronize.
void synchronizedFunction()
{
    // Synchronize this function.
    synchronized;

    // We are now synchronized.
}

这是宏的定义方式:
// Under the hood, we use a mutex to synchronize.
#include <mutex>

// Create a synchronized keyword.
#define CONCAT_INNER(a, b) a##b
#define CONCAT(a, b) CONCAT_INNER(a, b)
#define UNIQUE_NAME(base) CONCAT(base, __LINE__)
#define synchronized static std::mutex UNIQUE_NAME(syncMutex); std::unique_lock<std::mutex> syncLock(UNIQUE_NAME(syncMutex))

这对我来说在所有三个流行的编译器 {Windows,Clang,GCC} 上都有效。这个宏为函数创建一个唯一的静态互斥变量,并在该函数的作用域内立即对其进行锁定。它也可以在任何作用域内使用,不仅仅是函数作用域。
你可以尝试运行这个测试程序来证明它的有效性:
// Under the hood, we use a mutex to synchronize.
#include <mutex>

// Create a synchronized keyword.
#define CONCAT_INNER(a, b) a##b
#define CONCAT(a, b) CONCAT_INNER(a, b)
#define UNIQUE_NAME(base) CONCAT(base, __LINE__)
#define synchronized static std::mutex UNIQUE_NAME(syncMutex); std::unique_lock<std::mutex> syncLock(UNIQUE_NAME(syncMutex))

// For our test program below.
#include <iostream>
#include <thread>

// Define a function we want to synchronize.
void synchronizedFunction()
{
    // Synchronize this function.
    synchronized;

    // Print something, then sleep for 1 second, then print something else.
    // If we're synchronized, these two messages will appear back-to-back.
    // If we're not synchronized, these messages display in an uncontrolled fashion.
    std::cout << "Thread " << std::this_thread::get_id() << " is entering." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Thread " << std::this_thread::get_id() << " is exiting." << std::endl;
}

// A simple test program that will access our synchronized function from 2 asynchronous threads.
int main()
{
    // Construct a new thread and have it call our synchronized function.
    std::thread newThread(synchronizedFunction);

    // In our main thread, also call our synchronized function.
    synchronizedFunction();
    
    // Join both threads before exiting.
    newThread.join();
    
    // Exit.
    return 0;
}

当你运行这个程序时,你会得到以下输出--证明每个访问线程对函数的访问是串行的。你会注意到函数进入和退出时都有一秒的延迟(两次都是)。因为函数现在是串行的,所以测试程序将花费大约2秒钟来完成这个函数。
Thread 140737353824064 is entering.
Thread 140737353824064 is exiting.
Thread 140737257031424 is entering.
Thread 140737257031424 is exiting.

如果你将"synchronized;"语句注释掉,那么你会看到两个线程几乎同时进入函数,产生类似以下输出的结果,或者你可能会看到文本重叠在一起。因为我们不再同步,测试程序将需要大约1秒钟来完成,而不是2秒钟。
Thread 140737257031424 is entering.
Thread 140737353824064 is entering.
Thread 140737257031424 is exiting.
Thread 140737353824064 is exiting.

虽然这个宏的想法很酷,但我认为它太容易被滥用了。根据我的经验,在一个单独的函数中需要使用互斥锁的情况相当罕见。互斥锁大多数时候是用来保护一个对象而不是一个函数,并且在每次访问该对象时都需要对互斥锁进行加锁,通常至少在一个getter和一个setter中。如果你在这些getter和setter中使用synchronized宏,那么又会出现未定义的行为,因为我们需要锁定同一个互斥锁。虽然代码看起来是受保护的,但实际上并非如此。 - undefined
@prapin:为了清楚起见,我已编辑了我的答案,明确指出我的答案的上下文是为了同步对单个函数或方法的访问。 但是请记住,Java的"synchronized"关键字主要是以你不喜欢的方式使用的。 如果有人想要使整个类实例线程安全,我同意你,C++中有比尝试模拟"synchronized"更好的方法来实现这一点(例如std::lock_guardstd::mutex),但问题的标题是"在Java中,'synchronized'对应于[C++]的什么特性?",这是一个很好的解决方案。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接