同時問題研究(グラフィックコード詳細)

10764 ワード

コンピュータ履歴


At the time of begining、1台のコンピュータは最初から最後まで1つのプログラムを実行するしかなくて、資源を浪費します.
その後、オペレーティングシステムが登場しました.コンピュータはやっと複数のプログラムを実行することができます.実は、1つのプログラムは個別のプロセスです.1つのプロセスに1つ以上のスレッドがあり、cpuは異なるスレッドで実行を切り替え、cpuリソースを無駄にしません.

シリアルとパラレル:


シリアルとパラレル、パラレルプログラミングとは何ですか?同時プログラミングのメリットは何ですか?

スレッドが安全でない操作コードの例

package com.xdclass.synopsis;
import java.util.concurrent.CountDownLatch;
/**
 *  
 */
public class UnSafeThread {
    private static int num = 0;
    private static CountDownLatch countDownLatch = new CountDownLatch(10);
    /**
     *  num ++ 
     */
    public static void inCreate() {
        num++;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 100; j++) {
                    inCreate();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // , countdownLatch, 1
                countDownLatch.countDown();
            }).start();
        }
        while (true) {
            if (countDownLatch.getCount() == 0) {
                System.out.println(num);
                break;
            }
        }
    }
}

実行、num=937、予想された結果と一致しないことは明らかです.

スレッドの不安全な発生の原因を深く分析する

 javac -encoding UTF-8 UnsafeThread.java  .class
 javap -c UnsafeThread.class  , 

   0: getstatic     #2                , 
   3: iconst_1                          int 1 
   4: iadd                              int , 
   5: putstatic     #2                
   8: return

例では、スレッドの不安全問題の原因としてnum++が原子的な操作ではなく、いくつかのステップに分割され、マルチスレッドが同時に実行される場合、cpuスケジューリング、マルチスレッド宅配切替のため、2つの同じ時刻に同じnum値が読み取られ、その後+1操作される可能性があり、スレッドが安全ではない.

synchronized


組み込みロックjavaオブジェクトごとに同期を実現するロックとして使用できます.これらのロックは組み込みロックと呼ばれます.スレッドが同期コードブロックまたはメソッドに入ると自動的にロックが取得され、同期コードブロックまたはメソッドを終了するとロックが解放されます.
反発ロック内蔵ロックは反発ロックであり、これは最大1つのスレッドだけがロックを取得できることを意味し、スレッドAがスレッドBが持つ内蔵ロックを取得しようとすると、スレッドAはスレッドBがこのロックを解放するまで待機またはブロックしなければならず、Bスレッドがこのロックを解放しなければ、Aスレッドは永遠に待つことになる.
synchronizedはそれらの方法を修飾することができますか?
  • 修飾一般メソッド:オブジェクトのインスタンスをロックし、2つのスレッドが2つの異なるインスタンスにアクセスすると、ほとんど同時に出力を待つ必要がなく、別のインスタンスへのアクセスに影響がないことを示します.したがって、修飾一般メソッドは現在のインスタンスをロックするだけです.
  • 静的メソッドを修飾する:クラス全体をロックし、2つのスレッドが2つの異なるインスタンスにアクセスすると、1つのスレッドが出力された後に待機し、次のスレッドの出力の番になります.静的メソッドが現在のクラスをロックしていることを示します.このメソッドは危険で、実際の発行では推奨されません.
  • 修飾コードブロック:synchronized(lock)、すなわちsynchronizedの後ろの括弧の内容をロックし、括弧の内容を取得してこそ実行されます.
  • /**
     *  synchronized 
     */
    public class  SynDemo {
       // 
       public synchronized void out() throws InterruptedException {
           System.out.println(Thread.currentThread().getName());
           Thread.sleep(5000L);
       }
    
       // 
       public static synchronized void staticOut() throws InterruptedException {
       System.out.println(Thread.currentThread().getName());
       Thread.sleep(5000L);
    }
    
        // 
        private Object lock = new Object();
        public void myOut() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            SynDemo synDemo = new SynDemo();
    
    
            new Thread(() -> {
                    synDemo.myOut();
            }).start();
    
            new Thread(() -> {
                 synDemo.myOut();
            }).start();
    
        }
    }
    
    

    単一例とスレッドのセキュリティ


    1.一般的な書き方はどれらがありますか.
  • 餓漢式-----クラスロード時にクラスの内部にstaticキーワードを用いてクラス自体をインスタンス化する
  • .
  • 怠け者式---getInstance()メソッドにアクセスした場合,このクラスのインスタンスがなければnewは
  • であると判断する.
    2.この2つの単例の書き方はスレッドが安全ですか?すなわち、複数のスレッドがアクセスし、同じインスタンスが返されますか?
    package com.xdclass.safe;
    
    /**
     *  
     *  , , 。
     *  , , 。
     */
    public class HungerSingleton {
    
        private static HungerSingleton ourInstance = new HungerSingleton();
    
        public static HungerSingleton getInstance() {
            return ourInstance;
        }
        private HungerSingleton() {
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    System.out.println(HungerSingleton.getInstance());
                }).start();
            }
        }
    }

    このコードを実行すると,10個のスレッドが同時にアクセスし,同じインスタンスを出力することが分かったので,餓漢式の書き方はスレッドが安全である.
    怠け者の簡単な書き方
    /**
     *  
     *  
     */
    public class LazySingleton {
    
        private static LazySingleton lazySingleton = null;
    
        private LazySingleton() {
    
        }
    
        public static LazySingleton getInstance() {
            // , 
            if (null == lazySingleton) {
                // 
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }        
                lazySingleton = new LazySingleton();
                   
            }
            // 
            return lazySingleton;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    System.out.println(LazySingleton.getInstance());
                }).start();
            }
        }
    }

    このコードを実行すると、10個のスレッドが同時にアクセスし、出力されるのは同じインスタンスではないため、 はスレッドが安全ではないことがわかりました.どのように改善しますか?
    package com.xdclass.safe;
    
    /**
     *  
     *  
     */
    public class LazySingleton {
    
        private static volatile LazySingleton lazySingleton = null;
    
        private LazySingleton() {
    
        }
    
        public static LazySingleton getInstance() {
            // , 
            if (null == lazySingleton) {
                // 
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LazySingleton.class) {
                    if (null == lazySingleton) {
                        lazySingleton = new LazySingleton();
                    }
                }
            }
            // 
            return lazySingleton;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    System.out.println(LazySingleton.getInstance());
                }).start();
            }
        }
    
    }

    このコードは、volatileキーワードを使用して、命令の再ソートを防止します.またsynchronizedキーワードを用いることでインスタンス化操作が原子的であることが保証され、また、複数のスレッドが前のレイヤの判断に入ってnewが複数のインスタンスを出すことを防止し、スレッドの不安全を招く判断をさらに加える.そのため、この書き方こそスレッドが安全です.getInstance()メソッドに直接ロックしないのは、このような効率が低下するためである.

    スレッドプール


    スレッドプールを使用する理由
    同時スレッドの数が多く、各スレッドが短い時間で実行されるタスクが終了すると、頻繁にスレッドを作成すると、頻繁にスレッドを作成したり破棄したりするのに時間がかかるため、システムの効率が大幅に低下します.
    スレッドプールを作成するにはどうすればいいですか?Javaにはスレッドプールを作成するクラス:Executorが用意されていますが、作成時にはサブクラス:ThreadPoolExecutorが一般的に使用されます.
        public ThreadPoolExecutor(int corePoolSize,  
                                  int maximumPoolSize,  
                                  long keepAliveTime,  
                                  TimeUnit unit,  
                                  BlockingQueue workQueue,  
                                  ThreadFactory threadFactory,  
                                  RejectedExecutionHandler handler)

    これはこのクラスで最も重要な構造方法であり、この方法は作成されたスレッドプールの様々な属性を決定し、次に1枚の図に基づいてスレッドプールとこれらのパラメータをよりよく理解します.
    また、図では、スレッドプールのcorePoolSizeはスレッドプールのコアスレッドの数であり、これらのコアスレッドは、役に立たないときだけ回収されず、maximumPoolSizeはスレッドプールに収容できる最大スレッドの数であり、keepAliveTimeは、スレッドプールのコアスレッド以外の最長の保持時間であることがわかります.オンラインスレッドプールでは、コアスレッドを除いてはタスクがない場合でもクリアできないが、残りは生存時間がある.非コアスレッドが保持できる最長の空き時間を意味するが、utilは、この時間を計算する単位であり、workQueueは、待機キューであり、タスクはタスクキューに格納されて実行されるのを待つことができ、実行はFIFIO原則(先進先出)である.threadFactoryは、スレッドを作成するスレッドファクトリであり、最後のhandlerは、タスクがいっぱいになった後、いくつかのタスクの実行を拒否する拒否ポリシーです.
    スレッドプールの実行プロセスはどのようなものですか?
    public class Test {
         public static void main(String[] args) {   
             ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                     new ArrayBlockingQueue(5));
              
             for(int i=0;i<15;i++){
                 MyTask myTask = new MyTask(i);
                 executor.execute(myTask);
                 System.out.println(" :"+executor.getPoolSize()+", :"+
                 executor.getQueue().size()+", :"+executor.getCompletedTaskCount());
             }
             executor.shutdown();
         }
    }
     
     
    class MyTask implements Runnable {
        private int taskNum;
         
        public MyTask(int num) {
            this.taskNum = num;
        }
         
        @Override
        public void run() {
            System.out.println(" task "+taskNum);
            try {
                Thread.currentThread().sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task "+taskNum+" ");
        }
    }

    実行結果から、スレッドプール内のスレッドの数が5より大きい場合、タスクをタスクキャッシュキューに入れ、タスクキャッシュキューがいっぱいになると、新しいスレッドが作成されることがわかります.上記のプログラムでforループで20個のタスクを実行するように変更すると、タスク拒否例外が投げ出されます.最大スレッド数が足りないし、キューが足りないからです.参考:Java同時プログラミング:スレッドプールの使用図から分かるように、タスクが入ってきたら、まず判断を実行し、コアスレッドが空き状態にあるかどうかを判断し、そうでなければ、コアスレッドが先にタスクを実行し、コアスレッドがいっぱいになったら、タスクキューがそのタスクを保存する場所があるかどうかを判断し、もしあれば、タスクをタスクキューに保存し、実行を待つ.最大許容可能なスレッド数を判断し、この数を超えなければ非コアスレッド実行タスクを開き、超えたらhandlerを呼び出して拒否ポリシーを実現します.handlerの拒否ポリシー:
    4つあります:第1のAbortPolicy:この戦略は直接異常を投げ出して、システムの正常な動作を阻止します
    2つ目のDisCardPolicy:直接何もしないで、直接任務を捨てます
    3つ目のDisCardOldSetPolicy:最も古いリクエスト(タスクキューの最初のリクエスト)を破棄し、タスクのコミットを試みます.
    4つ目のCallerRunsPolicy:現在のタスクを実行するためにexecuteを直接呼び出す
    4つの一般的なスレッドプール:
    CachedThreadPool:キャッシュ可能なスレッドプールで、このスレッドプールにはコアスレッドがなく、非コアスレッドの数はIntegerである.max_valueは、無限大であり、必要に応じてスレッドを作成してタスクを実行し、必要がない場合はスレッドを回収し、時間が少なく、タスク量が大きい場合に適用されます.
    SecudleThreadPool:周期的にタスクを実行するスレッドプールで、ある特定の計画に従ってスレッド内のタスクを実行し、コアスレッドがあるが、非コアスレッドもあり、非コアスレッドのサイズも無限大である.周期的なタスクの実行に適しています.
    SingleThreadPool:タスクを実行するスレッドは1つしかありません.順序付きタスクの適用シーンに適用されます.
    FixedThreadPool:固定数のスレッドプールで、コアスレッドがあり、コアスレッドの数は最大のスレッド数であり、非コアスレッドはない.参照:スレッドとスレッドプールの詳細な理解

    スレッドプールの使用推奨事項


    Executorフレームワークを使用してスレッドプールを作成しないようにします.理由:
    newFixedThreadPool newSingleThreadExecutor
    許可されたリクエストキューの長さはIntegerです.MAX_VALUEでは、大量のリクエストが蓄積され、OOMが発生する可能性があります
    newCachedThreadPool newScheduledThreadPool
    作成できるスレッドの数はIntegerです.MAX_VALUEは、大量のスレッドを作成し、OOMを引き起こす可能性があります.
    なぜ2つ目の例は、スタックのメモリを限定した後、コンピュータ全体のメモリを爆発させるのか.
    スレッドを作成するときに使用するメモリは、jvmスタックメモリを作成するのではなく、システムの残りのメモリです.(コンピュータメモリ-システムの他のプログラムが使用するメモリ-予約済みjvmメモリ)
    スレッドプールを作成するときは、コアスレッドの数を大きくしないでください.
    対応するロジック、異常発生時に処理
    submit異常が発生した場合、すぐに投げ出すのではなく、getのときに異常を投げ出す
    execute直接放出異常