Java 101:Javaスレッドを理解する、パート1:スレッドとランナブルの紹介

この記事は、Javaスレッドを探求する4部構成のJava101シリーズの最初の記事です。Javaでのスレッド化は理解するのが難しいと思うかもしれませんが、スレッドが理解しやすいことをお見せしたいと思います。この記事では、Javaスレッドとランナブルを紹介します。以降の記事では、同期(ロックを介して)、同期の問題(デッドロックなど)、待機/通知メカニズム、スケジューリング(優先度の有無にかかわらず)、スレッドの中断、タイマー、揮発性、スレッドグループ、スレッドローカル変数について説明します。 。

この記事(JavaWorldアーカイブの一部)は、2013年5月に新しいコードリストとダウンロード可能なソースコードで更新されたことに注意してください。

Javaスレッドを理解する-シリーズ全体を読む

  • パート1:スレッドとランナブルの紹介
  • パート2:同期
  • パート3:スレッドのスケジューリングと待機/通知
  • パート4:スレッドグループとボラティリティ

スレッドとは何ですか?

概念的には、スレッドの概念を理解するのは難しくありません。それは、プログラムコードを介した独立した実行パスです。複数のスレッドが実行される場合、同じコードを通る1つのスレッドのパスは、通常、他のスレッドとは異なります。たとえば、あるスレッドがif-elseステートメントのif部分に相当するバイトコードを実行し、別のスレッドがそのelse部分に相当するバイトコードを実行するとします。 JVMはどのようにして各スレッドの実行を追跡しますか? JVMは、各スレッドに独自のメソッド呼び出しスタックを提供します。メソッド呼び出しスタックは、現在のバイトコード命令の追跡に加えて、ローカル変数、JVMがメソッドに渡すパラメーター、およびメソッドの戻り値を追跡します。

複数のスレッドが同じプログラムでバイトコード命令シーケンスを実行する場合、そのアクションはマルチスレッドと呼ばれます。マルチスレッドは、さまざまな方法でプログラムにメリットをもたらします。

  • マルチスレッドGUI(グラフィカルユーザーインターフェイス)ベースのプログラムは、ドキュメントの再ページングや印刷などの他のタスクを実行している間、ユーザーに応答し続けます。
  • スレッド化されたプログラムは、通常、スレッド化されていないプログラムよりも速く終了します。これは、各スレッドに独自のプロセッサがあるマルチプロセッサマシンで実行されているスレッドに特に当てはまります。

Javaは、そのjava.lang.Threadクラスを介してマルチスレッドを実現します。各Threadオブジェクトは、実行の単一スレッドを記述します。その実行はThreadrun()メソッドで発生します。デフォルトのrun()メソッドは何も実行しないため、有用な作業を実行するには、サブクラス化Threadしてオーバーライドrun()する必要があります。のコンテキストでのスレッドとマルチスレッドの好みについてはThread、リスト1を調べてください。

リスト1.ThreadDemo.java

// ThreadDemo.java class ThreadDemo { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); for (int i = 0; i < 50; i++) System.out.println ("i = " + i + ", i * i = " + i * i); } } class MyThread extends Thread { public void run () { for (int count = 1, row = 1; row < 20; row++, count++) { for (int i = 0; i < count; i++) System.out.print ('*'); System.out.print ('\n'); } } }

リスト1は、クラスThreadDemoMyThread。で構成されるアプリケーションのソースコードを示しています。クラスThreadDemoは、MyThreadオブジェクトを作成し、そのオブジェクトに関連付けられているスレッドを開始し、いくつかのコードを実行して正方形のテーブルを出力することにより、アプリケーションを駆動します。対照的に、のメソッドをMyThreadオーバーライドして、アスタリスク文字で構成される直角三角形を(標準出力ストリームに)印刷します。Threadrun()

スレッドスケジューリングとJVM

ほとんどの(すべてではないにしても)JVM実装は、基盤となるプラットフォームのスレッド機能を使用します。これらの機能はプラットフォーム固有であるため、マルチスレッドプログラムの出力の順序は、他の誰かの出力の順序とは異なる場合があります。この違いは、このシリーズの後半で説明するトピックであるスケジューリングに起因します。

入力java ThreadDemoしてアプリケーションを実行すると、JVMは実行の開始スレッドを作成し、main()メソッドを実行します。を実行することによりmt.start ();、開始スレッドは、MyThreadオブジェクトのrun()メソッドを構成するバイトコード命令を実行する2番目の実行スレッドを作成するようにJVMに指示します。場合start()メソッド戻り、開始スレッドがその実行for新しいスレッドの実行中、正方形のテーブルを印刷するためにループをrun()直角三角形を印刷する方法。

出力はどのように見えますか?ThreadDemo見つけるために実行します。各スレッドの出力が他のスレッドの出力と散在する傾向があることに気付くでしょう。これは、両方のスレッドが出力を同じ標準出力ストリームに送信するためです。

Threadクラス

マルチスレッドコードの記述に習熟するには、最初にThreadクラスを構成するさまざまなメソッドを理解する必要があります。このセクションでは、これらの方法の多くについて説明します。具体的には、スレッドの開始、スレッドの命名、スレッドのスリープ状態、スレッドが生きているかどうかの判断、あるスレッドの別のスレッドへの結合、現在のスレッドのスレッドグループとサブグループ内のすべてのアクティブなスレッドの列挙の方法について学習します。また、Threadのデバッグ支援とユーザースレッドとデーモンスレッドについても説明します。

ThreadSunの非推奨のメソッドを除いて、残りのメソッドについては後続の記事で説明します。

非推奨のメソッド

Sunは、プログラムをロックしたり、オブジェクトに損傷を与えたりする可能性があるため、やThreadなどのさまざまなメソッドを非推奨にしました。結果として、コードでそれらを呼び出さないでください。これらのメソッドの回避策については、SDKのドキュメントを参照してください。このシリーズでは、非推奨のメソッドについては説明しません。suspend()resume()

スレッドの構築

Thread8つのコンストラクターがあります。最も単純なものは次のとおりです。

  • Thread()Threadデフォルト名でオブジェクトを作成します
  • Thread(String name)、引数で指定されThreadた名前のオブジェクトを作成しますname

次の最も簡単なコンストラクタがあるThread(Runnable target)Thread(Runnable target, String name)Runnableパラメーターを除けば、これらのコンストラクターは前述のコンストラクターと同じです。違い:RunnableパラメーターThreadは、run()メソッドを提供する外部のオブジェクトを識別します。 (あなたはについて学びRunnable、この資料の後半。)最後の4つのコンストラクタは似ているThread(String name)Thread(Runnable target)Thread(Runnable target, String name)。ただし、最終的なコンストラクターには、ThreadGroup組織的な目的のための引数も含まれています。

最後の4つのコンストラクターの1つであるThread(ThreadGroup group, Runnable target, String name, long stackSize)、は、スレッドのメソッド呼び出しスタックの目的のサイズを指定できるという点で興味深いものです。そのサイズを指定できることは、再帰(メソッドがそれ自体を繰り返し呼び出す実行手法)を利用して特定の問題をエレガントに解決するメソッドを備えたプログラムで役立つことがわかります。スタックサイズを明示的に設定することで、StackOverflowErrorsを防ぐことができる場合があります。ただし、サイズが大きすぎるとOutOfMemoryErrorsになる可能性があります。また、Sunは、メソッド呼び出しスタックのサイズをプラットフォームに依存すると見なします。プラットフォームによっては、メソッド呼び出しスタックのサイズが変わる可能性があります。したがって、を呼び出すコードを作成する前に、プログラムへの影響について慎重に検討してくださいThread(ThreadGroup group, Runnable target, String name, long stackSize)

あなたの車を始動します

スレッドは車両に似ています。プログラムを最初から最後まで移動します。Threadそして、Threadサブクラスオブジェクトは、スレッドではありません。代わりに、名前などのスレッドの属性を記述run()し、スレッドが実行するコード(メソッドを介して)を含みます。新しいスレッドを実行するときが来るとrun()、別のスレッドがThread'sまたはそのサブクラスオブジェクトのstart()メソッドを呼び出します。たとえば、2番目のスレッドを開始するために、実行するアプリケーションの開始スレッドはをmain()呼び出しstart()ます。それに応じて、JVMのスレッド処理コードはプラットフォームと連携して、スレッドが適切に初期化され、Thread'sまたはそのサブクラスオブジェクトのrun()メソッドを呼び出すようにします。

完了start()すると、複数のスレッドが実行されます。我々は直線的に考える傾向があるので、私たちはしばしばそれが困難を理解することを見つける同時二つ以上のスレッドが実行されている場合に発生する(同時)の活動を。したがって、スレッドが実行されている場所(その位置)と時間の関係を示すグラフを調べる必要があります。下の図はそのようなチャートを示しています。

グラフは、いくつかの重要な期間を示しています。

  • 開始スレッドの初期化
  • スレッドが実行を開始した瞬間 main()
  • スレッドが実行を開始した瞬間 start()
  • その瞬間にstart()新しいスレッドが作成され、main()
  • 新しいスレッドの初期化
  • 新しいスレッドが実行を開始した瞬間 run()
  • 各スレッドが終了するさまざまな瞬間

Note that the new thread's initialization, its execution of run(), and its termination happen simultaneously with the starting thread's execution. Also note that after a thread calls start(), subsequent calls to that method before the run() method exits cause start() to throw a java.lang.IllegalThreadStateException object.

What's in a name?

During a debugging session, distinguishing one thread from another in a user-friendly fashion proves helpful. To differentiate among threads, Java associates a name with a thread. That name defaults to Thread, a hyphen character, and a zero-based integer number. You can accept Java's default thread names or you can choose your own. To accommodate custom names, Thread provides constructors that take name arguments and a setName(String name) method. Thread also provides a getName() method that returns the current name. Listing 2 demonstrates how to establish a custom name via the Thread(String name) constructor and retrieve the current name in the run() method by calling getName():

Listing 2. NameThatThread.java

// NameThatThread.java class NameThatThread { public static void main (String [] args) { MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = new MyThread (args [0]); mt.start (); } } class MyThread extends Thread { MyThread () { // The compiler creates the byte code equivalent of super (); } MyThread (String name) { super (name); // Pass name to Thread superclass } public void run () { System.out.println ("My name is: " + getName ()); } }

You can pass an optional name argument to MyThread on the command line. For example, java NameThatThread X establishes X as the thread's name. If you fail to specify a name, you'll see the following output:

My name is: Thread-1

If you prefer, you can change the super (name); call in the MyThread (String name) constructor to a call to setName (String name)—as in setName (name);. That latter method call achieves the same objective—establishing the thread's name—as super (name);. I leave that as an exercise for you.

Naming main

Java assigns the name main to the thread that runs the main() method, the starting thread. You typically see that name in the Exception in thread "main" message that the JVM's default exception handler prints when the starting thread throws an exception object.

To sleep or not to sleep

Later in this column, I will introduce you to animation— repeatedly drawing on one surface images that slightly differ from each other to achieve a movement illusion. To accomplish animation, a thread must pause during its display of two consecutive images. Calling Thread's static sleep(long millis) method forces a thread to pause for millis milliseconds. Another thread could possibly interrupt the sleeping thread. If that happens, the sleeping thread awakes and throws an InterruptedException object from the sleep(long millis) method. As a result, code that calls sleep(long millis) must appear within a try block—or the code's method must include InterruptedException in its throws clause.

実証するためにsleep(long millis)、私はCalcPI1アプリケーションを作成しました。そのアプリケーションは、数学アルゴリズムを使用して数学定数piの値を計算する新しいスレッドを開始します。新しいスレッドが計算している間、開始スレッドはを呼び出すことによって10ミリ秒一時停止しますsleep(long millis)。開始スレッドが起動すると、新しいスレッドが変数に格納するpi値が出力されますpi。リスト3はのCalcPI1ソースコードを示しています。

リスト3.CalcPI1.java

// CalcPI1.java class CalcPI1 { public static void main (String [] args) { MyThread mt = new MyThread (); mt.start (); try { Thread.sleep (10); // Sleep for 10 milliseconds } catch (InterruptedException e) { } System.out.println ("pi = " + mt.pi); } } class MyThread extends Thread { boolean negative = true; double pi; // Initializes to 0.0, by default public void run () { for (int i = 3; i < 100000; i += 2) { if (negative) pi -= (1.0 / i); else pi += (1.0 / i); negative = !negative; } pi += 1.0; pi *= 4.0; System.out.println ("Finished calculating PI"); } }

このプログラムを実行すると、次のような出力が表示されます(ただし、おそらく同一ではありません)。

pi = -0.2146197014017295 Finished calculating PI