スレッド分離による同時制御の簡素化


Javaでは,共有変数や状態への複数の異なるスレッドのアクセスを制限するために,言語が提供する同期やロックメカニズムを利用することが最も簡単で有効な方法である.ロックまたは同期により、同じ時間に共有変数またはターンテーブルにアクセスできるスレッドが1つしかないことを制御し、変数またはステータスの複数のスレッド間の一貫性と完全性を保証できます.ロックまたは同期は、スレッドへのアクセスを制限する必要があるすべての変数またはステータスに有効ですが、シーンによっては最適ではありません.すなわち,あるシナリオでは,ロックや同期によりマルチスレッド環境でのプログラムの正確性が確実に保証されるが,プログラムの性能に大きなダメージを与える.
次のコードは例を示します.Cookerクラスを定義しました.このクラスには、menuプロパティに基づいていくつかの操作を実行するcookメソッドがあります.menuに含まれる可能性のある情報には、どんな料理を作るか、具が何なのか、唐辛子を入れるかなどが含まれています.

public class Cooker {
    private Menu menu = null;

    public void setMenu(Menu menu) {
        this.menu = menu;
    }

    public void cook() {
        Menu menu = menu;
        //cook according to the menu
    }
}

この例では、各スレッドが使用している間に、新しいCookerクラスとMenuクラスを作成し、同時発生の問題を回避することができます.しかし、Cookerの作成プロセスが非常に時間がかかり、多くのシステムリソースを消費する必要がある場合、その実行プロセスがmenu属性の情報を取得する必要があるほか、共有されている属性やステータスがない場合、スレッドごとにCookerオブジェクトを作成するのは賢明ではありません.
この場合,Cookerオブジェクトにおけるmenu属性の同時問題を考慮する必要がある.複数のスレッドがmenuプロパティにアクセスする動作を制限しないと、多くの問題が発生する可能性があります.異なるスレッドがmenuプロパティを自由に設定できるため、あるスレッドがcookメソッドを実行するときに使用するmenuオブジェクトは、自分が最初に設定したmenuオブジェクトではなく、プログラムの実行結果が非常に奇妙になる可能性があります.
この問題はロックまたは同期によって解決できます.

class Kitchen {
    private Cooker cooker = new Cooker();

    public void doCook() {
        Menu menu = new Menu();
        synchronized (cooker) {
            cooker.setMenu(menu);
            cooker.cook();
        }
    }
}

上記の形式で、クッキーオブジェクトへのアクセスを同期ブロックに配置することで、毎回1つのスレッドだけがクッキーオブジェクトのmenu属性を設定し、cookメソッドを実行できることを制限することができます.それ以外にもdoCookメソッドを同期メソッドとして宣言すると同様の効果が得られます.また、同期が速い代わりに、ロックを表示することもできます.
以上の処理により,プログラムは正常に動作するようになったが,プログラムの同時性は大幅に低下した.では、プログラムの正確性を保証し、プログラムの同時性を高めることができるより良い方法はありませんか.答えはスレッド分離技術を使うことです.まず事例コードを見てみましょう

public class Cooker {
    Map<Thread, Menu> menuPerThreadMap = new ConcurrentHashMap<Thread, Menu>();

    public void setMenu(Menu menu) {
        Thread currentThread = getCurrentThread();
        menuPerThreadMap.put(currentThread, menu);
    }

    public void cook() {
        Thread currentThread = getCurrentThread();
        Menu menu = menuPerThreadMap.get(currentThread);

        if (menu != null) {
            //cook according to the menu
        }
    }

    private Thread getCurrentThread() {
        return Thread.currentThread();
    }
}

上のコードでは、現在のスレッドからMenuオブジェクトへのマッピング関係を維持する役割を果たすmenuPerThreadMapプロパティがCookerで定義されています.setMenuメソッドでは、現在のスレッドとそれに対応するMenuオブジェクトをこのmapに配置し、cookメソッドでは、現在のスレッドによってこのmapから対応するMenuオブジェクトを取り出し、その後の動作を実行します.このようにして,異なるスレッドで設定されたMenuオブジェクトを分離し,互いに影響を受けないようにし,プログラムの正確性を保証した.また,ロックや同期の必要性を排除し,プログラムの同時実行能力を向上させた.
上記の方法は基本的にスレッド分離の本質を述べているが、一般的に、あるクラスの属性が異なるスレッドの実行コンテキストによって異なる値を与えられ、クラスの動作に影響を与えるが、この属性の影響範囲は現在のスレッドに限られる(各スレッドは自分の状況に応じてこの属性を設定する)、スレッド分離技術は良い選択です.
もちろん、上記の実装は非常に粗雑で、1つの顕著な意見の問題はmenuPerThreadMapの要素をゴミ回収していないので、menuPerThreadMapはプログラムの実行に伴って増加し続け、最終的にjava仮想マシンのメモリオーバーフローを引き起こすことです.もちろん、ここでは処理する方法がありますが、その必要はありません.Javaはスレッド分離技術を有効に利用するために関連するクラスと方法を提供しているので、このクラスはThreadLocalです.
ThreadLocalクラスの基本思想は本論文で述べたスレッド分離技術と全く同じであるが,その実現はより複雑で信頼性が高く,汎用性だけでなく,ごみ回収問題もよく解決されている.関連ドキュメントおよびインプリメンテーションはJavaのAPIおよびソースコードを参照することができ、ここでは詳細に説明しない(インプリメンテーション全体も比較的簡単明瞭である).