このエントリーをはてなブックマークに追加

ポイントというか心にとまったことのアウトプットです。

※個人的な重要度のバイアスがかかっているので、「べつにいいかー」と思ってたりすることはメモしてません。

※手を動かして理解したことなど、間違いを含む可能性があります。

※手元の環境は断りがなければ基本はjava 7 をIntelliJ IDEA Ultimate on OSX で動かしてます。


「スレッド」

  • 実行状態=スレッド
  • CPUコア数以上のスレッド タイムスライス
    • スレッド同士の実行譲り合い
    • この譲り合い(CPUの奪い合い)は予測不可能
  • マルチスレッドプログラミング
    • 共有データの扱いに気をつける
      • 同期処理、排他制御
  • スレッド横取りが起きない操作=アトミックな操作
    • ロック
  • スレッド間で共有されないのは、変数の「値」。参照型変数の場合は、共有されうるので注意

  • 昨今開発者自身でスレッド生成することは少なくなっている(FWに隠蔽されるため)ので、複数スレッドから呼ばれても問題ないコードを書く技術が重要

  • Threadのjoinインスタンスメソッドは、start()のあとのrun()が終了するまでそこで待つ。

    • p.443のリスト17.5のjoin()は意味が無いが、join()の後に通常は何か書くはず(待ち合わせ後の処理)
  • Runnableインターフェース

    • @FunctionalInterface
    • new Thread のコンストラクタ引数にラムダ式を記述可能
      • でも処理が小さい(ラムダ式で記述するような)のに、Thread作るのはそもそもおすすめしない
  • Thread拡張を使うか、Runnableインターフェースを使うか

    • Runnableインターフェースを利用すべき(この本的には)
      • 拡張よりも委譲の方が依存性が弱まる
      • エントリポイントの実装と、Threadの生成という役割分担ができる
  • スレッドプール

    • 一度作成したスレッドを使い回す(プールしておく)
    • java.util.concurrent
    • ExecuterServiceのexecute()に、Runnableインターフェースの実装クラスのオブジェクトを渡す。(new Thread()の引数にRunnableを渡すのと同じイメージ)
    • ExecuterServiceのshutdown()とawaitTermination()
  • Runnableは値を返せないけど、Callableは値を返せる

    • Future
      • getメソッドでスレッドの戻り値を取得。まだ実行中だったら、実行が終わるまで停止する。タイムアウト値も設定可能。
    • CompletableFuture(java8)
      • 実行結果を待つコールバックが書ける
      • ☆thenで並列処理結果を待ちながらコールバックをつなぐ処理が書ける
  • synchronized

    • モニタロック
      • モニタロックはオブジェクト、Classオブジェクトに対してsynchronized呼び出しで獲得する
      • あるスレッドがモニタロックを獲得中、ほかのスレッドはそのモニタロックを獲得できない
public class TakudoNotSyncronized {

    private int _cnt = 0;

    public int getCnt(){
        return _cnt;
    }

    public void inc(){
        _cnt ++;
    }
}

public class TakudoSyncronized {
    private int _cnt = 0;

    public int getCnt(){
        return _cnt;
    }

    public synchronized void inc(){
        _cnt ++;
    }

}

TakudoNotSyncronized obj1 = new TakudoNotSyncronized();

Thread th1 = new Thread(() -> {
    for(int i = 0; i < 1000; i++){
        obj1.inc();
    }
});

Thread th2 = new Thread(() -> {
    for(int i = 0; i < 1000; i++){
        obj1.inc();
    }
});

th1.start();
th2.start();

th1.join();
th2.join();


System.out.println(obj1.getCnt()); // 2000になるときもあるけどならないときもある


TakudoSyncronized obj2 = new TakudoSyncronized();

Thread th3 = new Thread(() -> {
    for(int i = 0; i < 1000; i++){
        obj2.inc();
    }
});

Thread th4 = new Thread(() -> {
    for(int i = 0; i < 1000; i++){
        obj2.inc();
    }
});

th3.start();
th4.start();

th3.join();
th4.join();

System.out.println(obj2.getCnt()); // 2000になる!
  • ↑はsynchronized修飾子。synchronized文でも同様のことが可能。

    • synchronized文の方がロックを短くできる&任意のオブジェクト参照を指定できるので、自由度高い
  • クラスのモニタロックは、共有範囲が広いため非推奨

  • モニタロックに不変オブジェクト(String, Integerなど)を指定するとバグの元になる

  • volatile 修飾子

    • フィールドに使える
    • 同期処理にフィールドの読み書きだけ程度であれば、synchronizedよりもvolatileの方が効率的
    • long,doubleの代入もアトミック操作にできる(long,double以外の変数への代入はアトミック操作)
    • 変数の読み・書きのスレッド割り込みを防ぐものであり、一連した操作(たとえば++演算子。読み→計算→代入をしている)にはスレッド割り込みの問題はあるので、そこは要注意
  • 明示的ロック

    • java.util.concurrent.locks
    • unlockを忘れないよう、tryで囲い、finallyでunlockするようにする
    • ReadWriteLockで、読み書きに対応するロックを使うことができる
  • アトミック処理

    • java.util.concurrent.atomic
    • CAS(Compare And Swap)
    • AtomicInteger, AtomicLongなどを提供
    • volatile 修飾子は不要
    • 優位なのは実行速度(比較的)
  • 通知

    • wait()で待機させ(モニタロックの解放。CPU使用しない)とnotify()でwait()で止まっていたところから再開
    • 待つ側はconsumer、起こす側をproducer と呼ぶ
  • デッドロックは、ロックを入れ子にし、順番が異なるものを呼び出すと起こる。

    • ロックを入れ子にしない、入れ子にするときは順番を合わせれば、デッドロックを回避できる
    • ☆VisualVMや、☆Mission Controlでデッドロックを検出可能
  • コレクション用の同期オブジェクト Collectionsクラスで提供。

    • 読み込み・書き込みの間に割り込みの可能性があるので、一連で操作したいときはsynchronizedで囲う必要あり
  • 並行コレクションは実行性能いいけど、やはり割り込みには注意すべし