一. Java volatile
volatile关键字可保证变量可见性,但是无法保证原子性,下面演示多线程修改共享变量Count场景。
/**
* 共享变量在多线程下修改测试
*/
public class NonAtomicTest extends Thread {
public static volatile int count = 0;
public void increase(){
count++;
}
public void run(){
for(int i=0; i<10000; i++){
increase();
}
}
// main
public static void main(String[] args) {
Thread[] ths = new NonAtomicTest[20];
for(int i=0; i<ths.length; i++){
ths[i] = new NonAtomicTest();
ths[i].start();
}
while(Thread.activeCount() > 1){
Thread.yield();
}
System.out.println("Val: "+ count);
}
}
执行上述代码,发现每次执行count的值都不同,均小于200000。原因count++非原子操作,字节码执行过程:
GETSTATIC count // 从内存加载count到栈
ICONST_1 // 从栈中读取count值
IADD // 执行加法
PUTSTATIC count // 将执行结果存储至内存
对于上述问题,可对线程执行体加锁同步访问,但是加锁开销较大。Java中提供了AutomicInteger, AutomicLong 等原子操作类来处理上述问题,也是目前比较流行的无锁编程。原子操作由底层硬件 cmpxchg 指令支持,Linux 内核大量使用该指令。C++11也单独提供了原子类。
二. C++ 无锁编程
Linux下也提供原子操作API,C++11 std::aomic<T>模板均可进行无锁编程。
#include <iostream>
#include <vector>
#include <thread>
#include <atomic>
#include <mutex>
/// main
int main(int argc, char **argv)
{
static volatile int Count = 0;
std::mutex mutex;
// 方案一:互斥锁
auto fn1 = [&mutex, &Count]
{
for (int i=0; i<10000; i++)
{
std::lock_guard<std::mutex> lock(mutex);
Count++;
}
};
// 方案二:原子操作接口
auto fn2 = [&Count]
{
for (int i=0; i<10000; i++)
{
__sync_fetch_and_add(&Count, 1);
//__atomic_add_fetch(&Count, 1, __ATOMIC_SEQ_CST);
//__sync_bool_compare_and_swap(&Count, Count, Count + 1);
}
};
static std::atomic<int> Count(0);
// 方案三:原子类
auto fn3 = [&Count]
{
for (int i=0; i<10000; i++)
{
std::atomic_fetch_add(&Count, 1);
}
};
///
std::vector<std::thread> threads;
// 启动20个线程
for(int i=0; i<20; i++)
{
threads.push_back(std::thread(fn2));
}
// 等待线程执行完成
for (auto &th : threads)
{
th.join();
}
std::cout << "Val: " << Count << std::endl;
return 0;
}
g++ -o test test.c -std=c++11 -lpthread
上述三种方案均可打印200000