【同時プログラミング】マルチスレッドを実現するためのいくつかの方法

5703 ワード

このブログシリーズは、同時プログラミングを学習する際の記録のまとめです.文章が多くて、書く時間もバラバラなので、ディレクトリシール(転送ドア)を整理して、調べやすいです.
同時プログラミングシリーズブログ転送ゲート
Javaではマルチスレッドプログラミングを実現する方法がいくつかあります(これはよく聞く面接問題で、特に新卒で仕事を探しているときに聞かれる頻度が高いことを覚えています).
  • Threadクラスを継承しrunメソッドを書き換える.
  • はRunnableインタフェースを実装し、このクラスの例をtargetとしてThreadクラス
  • を構築する.
  • Callableインタフェースを実現する.

  • Threadクラスの継承


    Threadクラスを継承することでマルチスレッドプログラミングを実現することは容易である.次のコードでは、MyThreadクラスがThreadクラスを継承し、runメソッドを再記述します.
    しかし、この方法はあまり推奨されていません.その最も主要な原因はJavaが単一継承モードであり、MyThreadクラスがThreadクラスを継承した後、他のクラスを継承できないことです.だからimplementの形式を使うのは継承の方法よりもっと良いです.線面ではRunnableインタフェースを使用してマルチスレッドを実装することについて説明します.
    
    public class MyThread extends Thread {
    
        public static final int THREAD_COUNT = 5;
    
        public static void main(String[] args) {
    
            List threadList = new ArrayList<>();
    
            for (int i = 0; i < THREAD_COUNT; i++) {
                Thread thread = new MyThread();
                thread.setName("myThread--"+i);
                threadList.add(thread);
            }
            threadList.forEach(var->{var.start();});
        }
    
        @Override
        public void run() {
            super.run();
            System.out.println("my thread name is:"+Thread.currentThread().getName());
            Random random = new Random();
            int sleepTime = random.nextInt(5);
            try {
                TimeUnit.SECONDS.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
            }
        }
    }
    

    Runnableインタフェースの実装マルチスレッドの実装


    次に,Runnableインタフェースの形式を実現することによって,上のコードを改造する.
    Runnableインタフェースを実装することによってマルチスレッドプログラミングを実現することも非常に便利であることが分かった.しかし、Threadクラスを継承する必要はなく、結合が減少します.同時にnewがRunnerオブジェクトを1つ持つと,このオブジェクトは各スレッド間で比較的容易に共有できる.したがって,Threadを継承する方式よりもRunnableインタフェースを用いたマルチスレッドプログラミングが推奨される.
    
    public class MyThread {
    
        public static final int THREAD_COUNT = 5;
    
        public static void main(String[] args) {
    
            List threadList = new ArrayList<>();
            Runner runner = new Runner();
    
            for (int i = 0; i < THREAD_COUNT; i++) {
                Thread thread = new Thread(runner);
                thread.setName("myThread--"+i);
                threadList.add(thread);
            }
            threadList.forEach(var->{var.start();});
        }
    
    
        public static class Runner implements Runnable{
            @Override
            public void run() {
                System.out.println("my thread name is:"+Thread.currentThread().getName());
                Random random = new Random();
                int sleepTime = random.nextInt(5);
                try {
                    TimeUnit.SECONDS.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
                }
            }
        }
    
    }
    

    Callableインタフェースの実装


    両方の方式でマルチスレッドプログラミングを容易に実現できることを紹介した.しかし、この2つの方法にもいくつかの明らかな欠陥があります.
  • は値を返していません:実行結果を取得するには、変数を共有するなど、より多くの処理が必要です.
  • 異常を投げ出すことができません:宣言式の投げ出す異常を宣言することができなくて、いくつかの情況のプログラムの開発の複雑さを増加しました.
  • 手動でスレッドをキャンセルできません.スレッドの実行が完了するか、終了条件に達するまで待つしかありません.スレッドタスクを直接キャンセルすることはできません.

  • 以上の問題を解決するために、JDK 5バージョンのjava.util.concurretnパッケージに、新しいスレッド実装メカニズム:Callableインタフェースが導入された.
    
    @FunctionalInterface
    public interface Callable {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }
    

    Callableインタフェースの紹介を見て、実はこのインタフェースの機能はRunnableと同じで、Runnableインタフェースと最も主要な違いは:
  • Callableインタフェースには戻り値があり得る.
  • Callableインタフェースは異常を放出することができる.

  • 次に、Callableインタフェースを使用して、上のコードを改造します.
    public class MyThread {
    
        public static final int THREAD_COUNT = 5;
    
        public static void main(String[] args) throws Exception {
    
            ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
            Runner runner = new Runner();
    
            for (int i = 0; i < THREAD_COUNT; i++) {
                Future submit = executorService.submit(runner);
                //get 
                System.out.println(submit.get());
            }
            executorService.shutdown();
    
        }
    
    
        public static class Runner implements Callable {
    
            @Override
            public Integer call() throws Exception {
                System.out.println("my thread name is:"+Thread.currentThread().getName());
                Random random = new Random();
                int sleepTime = random.nextInt(500);
                try {
                    TimeUnit.SECONDS.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getName()+" end after "+sleepTime+" seconds");
                }
                return sleepTime;
            }
        }
    
    }

    上記のコードでは、Futureクラスを使用して戻り結果を取得します.Futureインタフェースの主な方法は次のとおりです.
  • isDone():タスクが完了したかどうかを判断します.
  • isCancelled():タスクがキャンセルされたかどうかを判断します.
  • get():計算結果を取得します(結果が得られるまで一貫して待機します).
  • cancel(true):タスクをキャンセルします.
  • get(long,TimeUnit):計算結果を所定時間内に取得する(long時間内に結果を待ち、得られたら返す;得られなければ終了し、TimeoutException異常を投げ出す).