同時実行性とは、複数のプログラムを並行して実行する、またはプログラムの複数の部分を並行して実行する機能です。プログラム内の時間のかかるタスクを非同期または並列で実行できれば、プログラム全体のスループットと対話性が大幅に向上します。最近の PC には複数の CPU または 1 つの CPU 内に複数のコアが搭載されており、複数のコアを適切に使用できるかどうかが、大規模なアプリケーションの鍵となります。
スレッドの基本的な使い方
スレッドの実行中に実行されるコードを記述する方法は 2 つあります。1 つは Thread サブクラスのインスタンスを作成して run メソッドをオーバーライドする方法、もう 1 つはスレッドの実行時に Runnable インターフェイスを実装する方法です。クラスを作成しています。もちろん、Callableを実装するという手もありますが、CallableとFutureの組み合わせではタスク完了後の戻り値を取得できますが、RunnableメソッドやThreadメソッドではタスク実行後の結果を取得できません。
public class ThreadMain { public static void main(String[] args) { MyThread myThread = new MyThread(); new Thread(myThread).start(); new MyThreas2().start(); } }// 第一种方式,实现Runable接口class MyThread implements Runnable { @Override public void run() { System.out.println("MyThread run..."); } }// 第二种方式,继承Thread类,重写run()方法class MyThreas2 extends Thread { @Override public void run() { System.out.println("MyThread2 run..."); } }
スレッドが開始されると、start() メソッドは、run() メソッドの実行が完了するのを待たずに、あたかも run メソッドが別の CPU で実行されたかのように、すぐに戻ります。
注: スレッドを作成して実行するときによくある間違いは、以下に示すように、start() メソッドの代わりにスレッドの run() メソッドを呼び出すことです。 () メソッドは実際に期待どおりに呼び出されます。ただし、実際には、 run() メソッドは作成されたばかりの新しいスレッドによって実行されるのではなく、現在のスレッドによって実行されます。つまり、上記の 2 行のコードを実行するスレッドによって実行されます。作成した新しいスレッドで run() メソッドを実行したい場合は、新しいスレッドの start メソッドを呼び出す必要があります。
Callable と Future を組み合わせて、タスク実行後の戻り値を取得します:
Thread newThread = new Thread(MyRunnable()); newThread.run(); //should be start();
スレッドのスレッド名を設定します:
public static void main(String[] args) { ExecutorService exec = Executors.newSingleThreadExecutor(); Future<String> future = exec.submit(new CallTask()); System.out.println(future.get()); }class CallTask implements Callable { public String call() { return "hello"; } }
MyTask myTask = new MyTask(); Thread thread = new Thread(myTask, "myTask thread");
スレッドを作成するときに、スレッドに名前を付けることができます。これは、異なるスレッドを区別するのに役立ちます。
volatile
synchronized と volatile は両方とも、マルチスレッド同時プログラミングにおいて重要な役割を果たします。Volatile は軽量の同期型であり、マルチプロセッサ開発における共有変数の「可視性」を保証します。可視性とは、あるスレッドが共有変数を変更すると、別のスレッドが変更された値を読み取ることができることを意味します。場合によっては、synchronized よりもコストが低くなりますが、volatile では変数のアトミック性を保証できません。
揮発性変数が書き込まれるとき (アセンブリの下にロック命令がある)、マルチコア システムではロック命令には次の 2 つの機能があります:
現在の CPU キャッシュ ラインをシステム メモリに書き戻す。
このライトバック操作により、他の CPU によってキャッシュされたデータのアドレスが変更され、無効になります。複数の CPU はキャッシュ一貫性の原則に従い、各 CPU はバス上で送信されたデータを傍受することでキャッシュ値が期限切れかどうかを確認し、キャッシュに対応するメモリ アドレスが変更されていることが判明すると、対応するキャッシュ ラインを次のように設定します。無効な状態の場合、次のデータ操作はシステム メモリから再読み取りされます。 Volatile についてさらに詳しく知りたい場合は、クリックして Volatile の実装原理の詳細な分析をご覧ください。
synchronized
Synchronized は常にマルチスレッド同時プログラミングのベテランです。しかし、Java SE1.6 では Synchronized がさまざまに最適化されているため、それほど重くない場合もあります。
Java のすべてのオブジェクトはロックとして使用できます。スレッドが同期されたコード ブロックにアクセスしようとするときは、まずロックを取得し、終了するか例外をスローするときにロックを解放する必要があります。
同期メソッドの場合、ロックは現在のインスタンス オブジェクトです。
静的同期メソッドの場合、ロックは現在のオブジェクトの Class オブジェクトです。同期メソッド ブロックの場合、ロックは同期ブラケットで設定されたオブジェクトです。
synchronized キーワードは継承できません。これは、基本クラスの synchronized メソッドがサブクラスでデフォルトで同期されないことを意味します。スレッドが同期されたコード ブロックにアクセスしようとすると、まずロックを取得し、終了するか例外をスローするときにロックを解放する必要があります。 Java のすべてのオブジェクトはロックとして使用できます。では、ロックはどこにあるのでしょうか?ロックは Java オブジェクト ヘッダーに格納されます。オブジェクトが配列型の場合、仮想マシンはオブジェクト ヘッダーの格納に 3 ワード (ワード幅) を使用します。オブジェクトが非配列型の場合、仮想マシンは 2 ワードを使用します。 (ワード幅) オブジェクトヘッダーを格納します。同期に関するさらに詳しい情報については、「Java SE1.6 での同期」をクリックしてください。
スレッド プール
スレッド プールはワーカー スレッドの管理を担当し、実行を待機しているタスクのキューが含まれています。スレッドプールのタスクキューはRunnableの集合であり、ワーカースレッドはタスクキューからRunnableオブジェクトを取り出して実行する役割を担います。
thread.start(); System.out.println(thread.getName());
Java は Executor を通じて 4 種類のスレッド プールを提供します:
newCachedThreadPool: 新しいタスクの場合、アイドル状態のスレッドがない場合、アイドル状態のスレッドが一定の時間を超えると、新しいスレッドが作成されます。リサイクルされることになります。
newFixedThreadPool: 固定数のスレッドを持つスレッド プールを作成します。
newSingleThreadExecutor: タスクの実行に 1 つのスレッドのみを使用するシングルスレッド スレッド プールを作成し、すべてのタスクが FIFO 順序で実行されるようにします。
newScheduledThreadPool: スケジュールされた定期的なタスクの実行をサポートするために、固定長のスレッド プールを作成します。
上記のスレッド プールの最下層はすべて ThreadPoolExecutor を呼び出してスレッド プールを作成します。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。,可以选择的阻塞队列有以下几种:
workQueue(任务队列):用于保存等待执行的任务的阻塞队列。
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级得无限阻塞队列。
当提交新任务到线程池时,其处理流程如下:
1.先判断基本线程池是否已满?没满则创建一个工作线程来执行任务,满了则进入下个流程。
2.其次判断工作队列是否已满?没满则提交新任务到工作队列中,满了则进入下个流程。
3.最后判断整个线程池是否已满?没满则创建一个新的工作线程来执行任务,满了则交给饱和策略来处理这个任务。