javaメモ--スレッド同期について(7つの同期方式)

25455 ワード

スレッド同期について(7つの方式)
 --友達がこの記事を転載したいなら、転載住所「http://www.cnblogs.com/XHJT/p/3897440.html」を明記してください.ありがとうございます. 
なぜ同期を使うのですか?    javaはマルチスレッドの同時制御を可能にし、複数のスレッドが同時に共有可能なリソース変数を操作する場合(データの添削など)、    データが不正確になり、互いに衝突してしまうので、スレッドが動作していない前に他のスレッドに呼び出されないように同期ロックをかけます.    これによりこの変数の一意性と精度が保証される.
 
1.同期方法    synchronizedキーワードを修飾する方法があります.    javaの各オブジェクトに内蔵ロックがあるので、このキーワードで修飾する場合、    内蔵ロックは全体を保護します.この方法を起動する前に、内蔵ロックを取得する必要があります.そうでないと閉塞状態になります.
    コード:    public synchronized void save()
   注:synchronizedキーワードも静的方法を修飾することができます.この静的方法を起動すると、クラス全体がロックされます.
 
2.同期コードブロック    synchronizedキー修飾の語句ブロックがあります.    このキーに修飾されたステートメントブロックは自動的に内蔵ロックされ、同期が可能になります.
    コード:    synchronized{object}    }
    注:同期は高いオーバーヘッドの操作ですので、同期の内容をできるだけ減らすべきです.    通常は、全体の方法を同期させる必要がないので、synchronizedコードブロックを使用して、キーコードを同期すればいいです.         コードの例:    
package com.xhj.thread;



    /**

     *        

     * 

     * @author XIEHEJUN

     * 

     */

    public class SynchronizedThread {



        class Bank {



            private int account = 100;



            public int getAccount() {

                return account;

            }



            /**

             *        

             * 

             * @param money

             */

            public synchronized void save(int money) {

                account += money;

            }



            /**

             *         

             * 

             * @param money

             */

            public void save1(int money) {

                synchronized (this) {

                    account += money;

                }

            }

        }



        class NewThread implements Runnable {

            private Bank bank;



            public NewThread(Bank bank) {

                this.bank = bank;

            }



            @Override

            public void run() {

                for (int i = 0; i < 10; i++) {

                    // bank.save1(10);

                    bank.save(10);

                    System.out.println(i + "     :" + bank.getAccount());

                }

            }



        }



        /**

         *     ,     

         */

        public void useThread() {

            Bank bank = new Bank();

            NewThread new_thread = new NewThread(bank);

            System.out.println("  1");

            Thread thread1 = new Thread(new_thread);

            thread1.start();

            System.out.println("  2");

            Thread thread2 = new Thread(new_thread);

            thread2.start();

        }



        public static void main(String[] args) {

            SynchronizedThread st = new SynchronizedThread();

            st.useThread();

        }



    }
     3.特殊ドメイン変数(volatile)を使ってスレッド同期を実現する
    a.volatileキーワードはドメイン変数へのアクセスにロックフリー機構を提供しています.    b.volatileを使ってドメインを修飾するということは、仮想マシンにこのドメインが他のスレッドによって更新される可能性があるということに相当する.    c.したがって、このドメインを使用するたびに、レジスタの値を使用するのではなく、再計算される.    d.volatileはいかなる原子操作も提供しないし、finalタイプの変数を修飾するためにも使用できない.        たとえば:        上記の例では、accountの前にvolatile修飾を加えるだけで、スレッド同期が可能です.        コードの例:    
      //

        class Bank {

            //         volatile

            private volatile int account = 100;



            public int getAccount() {

                return account;

            }

            //      synchronized 

            public void save(int money) {

                account += money;

            }

        }
    注:マルチスレッドにおける非同期問題は主にドメインの読み書きにおいて発生します.ドメイン自体がこの問題を回避するなら、ドメインを操作する方法を変更する必要はありません.    finalドメインでは、ロック保護されたドメインとvolatil eドメインがあり、非同期の問題を回避することができる.    4.マルチロックを使ってスレッド同期を実現する
    JavaSE 5.0には、同期をサポートするためにjava.util.co ncurrentパッケージが追加されました.    RentrantrantLock類は再入力、相互反発、ロックインターフェースのロックを実現しています.    synchronized法と高速を使って同じ基本的な行動と意味を持ち、その能力を拡張しました.
    Reenreant Lock類の常用方法は以下の通りです.
        Reentrant Lock():Reentrant Lockの例を作成する        ロック():ロックを獲得する        ロック():リリースロック    注:RentrantrantLock()もう一つは公平なロックを作ることができる構造方法がありますが、プログラムの運行効率を大幅に下げることができますので、使用を推奨しません.            たとえば:        上記の例に基づいて、書き換え後のコードは以下の通りです.            コードの例:    
//

        class Bank {

            

            private int account = 100;

            //       

            private Lock lock = new ReentrantLock();

            public int getAccount() {

                return account;

            }

            //      synchronized 

            public void save(int money) {

                lock.lock();

                try{

                    account += money;

                }finally{

                    lock.unlock();

                }

                

            }

        }
              注:ロックオブジェクトとsynchronizedキーワードの選択について:        a.できれば両方を使わないでください.java.util.co ncurrentパッケージで提供される仕組みを使って、            ユーザーがロックに関するコードをすべて処理するのを助けることができます.        b.synchronizedキーワードがユーザーのニーズを満たすことができれば、synchronizedを使います.コードを簡略化できるからです.        c.より高度な機能が必要な場合は、Reentrant Lock類を使用し、この時はロックを解除することに注意してください.そうでないとデッドロックが発生します.通常はfinallyコードでロックを解除します.        5.ローカル変数を使ってスレッド同期を実現する    ThreadLocalを使って変数を管理すると、その変数を使用するスレッドごとにその変数のコピーを取得します.    コピー間は独立しています.このように各スレッドは自分の変数コピーを勝手に修正できます.他のスレッドに影響を与えません.
 
    ThreadLocal類の常用方法
 
    ThreadLocal():スレッドローカル変数を作成します.    get():このスレッドのローカル変数の現在のスレッドコピーの値を返します.    initial Value():このスレッドのローカル変数の現在のスレッドの「初期値」を返します.    set(T value):このスレッドのローカル変数の現在のスレッドのコピーの値をvalueに設定します.
 
    たとえば:        上記の例に基づいて、修正後のコードは:            コードの例:        
//  Bank ,       

        public class Bank{

            //  ThreadLocal       account

            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){

                @Override

                protected Integer initialValue(){

                    return 100;

                }

            };

            public void save(int money){

                account.set(account.get()+money);

            }

            public int getAccount(){

                return account.get();

            }

        }
    注:ThreadLocalと同期機構        a.ThreadLocalと同期機構は、マルチスレッド中の同じ変数のアクセス衝突問題を解決するためである.        b.前者は「空間交換」という方法を採用しています.後者は「時間の空間交換」という方法を採用しています.
 
 
 
6.ブロック列を使ってスレッド同期を実現する
 
    前の5つの同期方式はすべて下の層で実現されたスレッド同期であるが,実際の開発においては,下の構造からできるだけ遠く離れるべきである.     javaSE 5.0バージョンで追加されたjava.util.co ncurrentパッケージを使用すると、開発の簡素化に役立ちます.     この小節は主にLinked Blocking Que<E>を使ってスレッドの同期を実現します.     Linked Blocking Que<E>は、接続されているノードに基づいて、任意の範囲のブロックキングqueueである.     列は先着順(FIFO)です.列については後で詳しく説明します.        Linked Blocking Que類の常用方法     Linked Blocking Que():Integer.MAXUVALEのLinked Blocking Queを作成します.     put(E e):列の最後に要素を追加します.列がいっぱいになると閉塞します.     size():キューの要素の個数を返します.     Take():削除してチームヘッドの要素に戻り、列が空いたらブロックされます.        コードの例:         商品の生産と販売の同期を実現する.
 1 package com.xhj.thread;

 2 

 3 import java.util.Random;

 4 import java.util.concurrent.LinkedBlockingQueue;

 5 

 6 /**

 7  *             LinkedBlockingQueue   

 8  * 

 9  * @author XIEHEJUN

10  * 

11  */

12 public class BlockingSynchronizedThread {

13     /**

14      *                    

15      */

16     private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();

17     /**

18      *         

19      */

20     private static final int size = 10;

21     /**

22      *          , 0 ,         ; 1 ,         

23      */

24     private int flag = 0;

25 

26     private class LinkBlockThread implements Runnable {

27         @Override

28         public void run() {

29             int new_flag = flag++;

30             System.out.println("     " + new_flag);

31             if (new_flag == 0) {

32                 for (int i = 0; i < size; i++) {

33                     int b = new Random().nextInt(255);

34                     System.out.println("    :" + b + " ");

35                     try {

36                         queue.put(b);

37                     } catch (InterruptedException e) {

38                         // TODO Auto-generated catch block

39                         e.printStackTrace();

40                     }

41                     System.out.println("       :" + queue.size() + " ");

42                     try {

43                         Thread.sleep(100);

44                     } catch (InterruptedException e) {

45                         // TODO Auto-generated catch block

46                         e.printStackTrace();

47                     }

48                 }

49             } else {

50                 for (int i = 0; i < size / 2; i++) {

51                     try {

52                         int n = queue.take();

53                         System.out.println("      " + n + "   ");

54                     } catch (InterruptedException e) {

55                         // TODO Auto-generated catch block

56                         e.printStackTrace();

57                     }

58                     System.out.println("       :" + queue.size() + " ");

59                     try {

60                         Thread.sleep(100);

61                     } catch (Exception e) {

62                         // TODO: handle exception

63                     }

64                 }

65             }

66         }

67     }

68 

69     public static void main(String[] args) {

70         BlockingSynchronizedThread bst = new BlockingSynchronizedThread();

71         LinkBlockThread lbt = bst.new LinkBlockThread();

72         Thread thread1 = new Thread(lbt);

73         Thread thread2 = new Thread(lbt);

74         thread1.start();

75         thread2.start();

76 

77     }

78 

79 }
 
注:BlockingQue<E>は列を塞ぐための一般的な方法を定義しています.特に3つの要素を追加する方法は、列がいっぱいになる時に注意してください.
add()方法は異常を投げます.
方法はfalseに戻ります.
put()方法は詰まります.
 
 
7.原子変数を使ってスレッド同期を実現する
 
スレッド同期を必要とする根本的な原因は,通常の変数に対する動作が原子ではないことにある.
原子操作とは何ですか?原子操作とは、変数値の読み取り、変数値の変更、保存変数値を一つの全体として扱うことです.これらの行為は同時に行われるか、あるいは全部完了しないかを指します.javaのutil.co ncurrent.atomicパッケージでは、元のサブタイプ変数を作成するツールクラスが提供されています.このクラスを使ってスレッド同期を簡単にすることができます.AtomicInteger表は原子式でintの値を更新することができます.アプリケーションでは(原子式で増加したカウンタのような)、Integerの置換はできません.Numberを拡張して、それらの処理機会の数字類のツールと実用ツールを統合してアクセスすることができます.AtomicInteger類の一般的な方法:AtomicInteger(int initialValue):与えられた初期値を持つ新しいAtomicInteger addAddGetを作成します.与えられた値を原子的に現在の値と加算してget():現在の値コードを取得した例:Bankクラスだけを変更し、残りのコードは上記の第一例と同じです.
 1 class Bank {

 2         private AtomicInteger account = new AtomicInteger(100);

 3 

 4         public AtomicInteger getAccount() {

 5             return account;

 6         }

 7 

 8         public void save(int money) {

 9             account.addAndGet(money);

10         }

11
補足--原子の操作は主に参照変数とほとんどの元の変数(longとdoubleを除く)に対する読み書き操作と、volatileを使用した変数(longとdoubleを含む)の読み書き操作があります.