久々に volatileが使われているソース
しかも使い方間違ってて、ちゃんと同期出来てないものに
出会ったので、確認コード
複数のスレッドから、同じカウンターをインクリメントするサンプル
package test;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class Sync {
private final int THREAD_NUM = 50;
private final int COUNT_NUM = 1000;
private static Sync instance = new Sync();
private int mCounter=0;
private volatile int mVolatileCounter=0;
private Object mSyncObj = new Object();
private AtomicInteger mAtomCounter = new AtomicInteger(0);
public static void main(String[] args) {
instance._run( "dummy", instance._no_sync );
instance.mCounter = 0;
instance._run( "_no_sync", instance._no_sync );
instance.mCounter = 0;
instance._run( "_synchronized", instance._synchronized );
instance._run( "_volatile", instance._volatile );
instance._run( "_atom", instance._atom );
instance.mAtomCounter.set(-1);
instance._run( "_lock_free", instance._lock_free );
}
private Runnable _no_sync =
new Runnable(){
public void run() {
for (int i = 0; i < COUNT_NUM; i++) {
System.out.print( mCounter++ + ",");
}
}
};
private Runnable _synchronized =
new Runnable(){
public void run() {
for (int i = 0; i < COUNT_NUM; i++) {
synchronized(mSyncObj){
System.out.print( mCounter++ + ",");
}
}
}
};
private Runnable _volatile =
new Runnable(){
public void run() {
for (int i = 0; i < COUNT_NUM; i++) {
System.out.print( mVolatileCounter++ + ",");
}
}
};
private Runnable _atom =
new Runnable(){
public void run() {
for (int i = 0; i < COUNT_NUM; i++) {
System.out.print( mAtomCounter.getAndIncrement() + ",");
}
}
};
private Runnable _lock_free =
new Runnable(){
public void run() {
for (int i = 0; i < COUNT_NUM; i++) {
int n;
do{
n = mAtomCounter.get();
}
while(!mAtomCounter.compareAndSet(n, n+1));
// print前に他スレッドからcompareAndSetが呼ばれ、出力がずれるので修正
// System.out.print( mAtomCounter.get() + ",");
System.out.print( n+1 + ",");
}
}
};
public void _run( String sMsg, Runnable runnable){
System.out.println( "[" + sMsg + "] Start!!");
long start = System.currentTimeMillis();
ArrayList<Thread> thread = new ArrayList<Thread>();
for( int i=0; i<THREAD_NUM; i++ ){
Thread tt = new Thread( runnable );
thread.add(tt);
tt.start();
}
for( Thread t : thread ){
try {
t.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("\n" + sMsg + ": " + (System.currentTimeMillis() - start) + "ms");
}
}
C++ゲンガーなので変な書き方だったらすみません。
まず、今回は コンソール出力が重いので 時間は無視して
#結果の見方
synchronized以外は、出力までatomic性を求めてないので
数値の順番はどうでもよい。
重複(抜け)があると、同期に問題がある
データを スレッド起動より500ms待機させ
よりスレッドの衝突がしやすいものにかえた
(asahina_devさんありがとうございます)
ので、スレッド数10、ループ3回で差が出たので
そのデータにさしかえました
LockFreeがConsole出力前に他スレッドがカウンター書き換える可能性があるので
少し修正しました
同期しない:
2,4,6,4,3,3,11,2,14,2,16,15,13,12,10,9,7,8,6,5,23,22,21,20,19,18,17,26,25,24,
重複、抜けがあります
抜け:0,1,27,28,29
重複:2,2,3,4,6,
synchronized:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29
パーフェクト!
もちろん出力までsynchronizedしてるので出力までAtomic
volatile:
0,2,0,4,5,1,0,6,3,8,7,9,12,14,13,10,11,19,18,17,16,15,24,23,22,21,20,25,26,27,
重複、抜けあり
抜け:28,29,30
重複:0,0,0,
atomic:
1,8,4,6,5,2,3,7,16,18,15,14,13,12,11,10,0,9,25,24,23,22,21,20,19,17,28,27,26,29,
重複データなし。Atomic性保たれてます
LockFree:
0,3,4,5,2,8,6,7,1,16,15,14,13,12,11,10,9,24,23,22,21,20,19,18,17,25,26,27,28,29,
atomicと同じく重複データなし。Atomic性保持。
#速度(参考)
データ量を100倍、スレッド数2倍にして
Console出力命令を消し
各機能の10回平均
同期なし: 7ms
そりゃー速い!!
volatile: 180ms
速いけど、カウンターとして正しく動作しない
synchronized: 403ms
意外に速かった!!
カウンター以外の処理もAtomic性を保てるので
基本的に synchronized使えばよくね?
atomic: 1089ms
予想外に遅かった・・・
でも、条件によってはsynchronizedより速い事もある・・はず
lock_free: 1094ms
多分 getAndIncrement等は、8086系では少なくとも compareAndSet
で実装してるのでしょう
上記とだいたい同じ
#結論
volatileは同期されると思うな。volatileは基本 コンパイル最適か抑制。
同期したければ素直に synchronized 使う
Atomic系の処理は、よく考えて使おう。synchronizedの方が速いケースも多い
間違い指摘あったらおねがいします。