LoginSignup
41
41

More than 5 years have passed since last update.

volatileで排他制御出来ると、いつから思ってた? atomicやsynchronized使うべし

Last updated at Posted at 2014-03-15

久々に volatileが使われているソース
しかも使い方間違ってて、ちゃんと同期出来てないものに
出会ったので、確認コード

複数のスレッドから、同じカウンターをインクリメントするサンプル

test.java
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の方が速いケースも多い

間違い指摘あったらおねがいします。

41
41
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
41