以前的理解,用volatile 关键字 修饰的变量, 可以在多个线程之间同步最新的值.
当A线程更改值后,B线程马上可以获取到.
我这样的理解对吗?
但是使用的过程中,发现并不是这样的,某些线程修改的值会被忽略掉..
简单的Demo如下:static volatile int callCount = 0;
static object ob = new object(); static void ThreadTest() { ++callCount; Console.WriteLine("CallCount="+callCount); } static volatile int callCount = 0; static void Main(string[] args) { for (int i = 0; i < 1000; i++) { Thread t = new Thread(new ThreadStart(() => { ThreadTest(); })); t.Start(); } Console.WriteLine("等一段时间让后台线程跑完"); Thread.Sleep(10000); Console.WriteLine("最终被调用的次数为:" + callCount); Thread.Sleep(10000); Console.Read(); }
直接在VS里按F5运行的效果如下:
为什么?本来应该是1000才对的啊.
可是在生成的bin目录下,双击运行,却始终是1000
这是为什么呢?
volatile并不保证线程安全性,它只保证任何时候你读取到的都是最新值,你int的加法又不是原子性操作。
这块你要换成Interlocked.Increment/Decrement(cas操作),并去掉volatile(没意义了,cas内部保障)。
ps:volatile性能并不好,很多时候你可以继续优化,比如
int Read(){
Thread.MemoryBarrier();
return x;
}
将你的代码改装下
static object ob = new object(); static void ThreadTest() { ++callCount; Console.WriteLine("CallCount=" + callCount); lock (ob) { counter++; } } static int counter = 0; static volatile int callCount = 0; static void Main(string[] args) { for (int i = 0; i < 1000; i++) { Thread t = new Thread(new ThreadStart(() => { ThreadTest(); })); t.Start(); } Console.WriteLine("等一段时间让后台线程跑完"); Thread.Sleep(10000); Console.WriteLine("最终被调用的次数为:" + callCount); Console.WriteLine("counter:" + counter); Thread.Sleep(10000); Console.Read(); }
因为并发的时候,重复计数了,算做+1
楼主测试的是volatile关键字。不是锁。。
@waiter: 看文字
@上帝之城: 楼主问的是深层次的原因, 你一个lock把counter++的取值、计算、赋值三步操作都个锁住了,问题的原因就在于多线程中这三步会发生很多交叉执行~
volatile只是保证你每次读取的值都是最新的,我的猜测是 debug情况下,出现多次thread同时读取该值的概率相对大。所以存在多个线程在执行++callCount操作时,callCount是一样的。
++callCount表示 callCount=callCount+1;
第一步,callCount+1时做取值操作; 这一步volatile保证所有线程取的都是最新的。。
第二步,callCount=??时做赋值操作;这一步就是个赋值操作,volatile保证你的赋值操作正确。
因为是多线程,所以你前脚存了801,我后脚存了800.
@waiter: 谢谢..我以为有了volatile后,就变成原子操作了..原来不是的,和楼下说的一样.