ホームページ Java &#&チュートリアル Java Threadマルチスレッドの包括的な分析

Java Threadマルチスレッドの包括的な分析

Jan 05, 2017 pm 03:27 PM

マルチスレッドは Java の非常に重要な知識ポイントです。ここでは、エディターが Java スレッドのマルチスレッドについてまとめますので、ぜひマスターしてください。

1. スレッドのライフ サイクルと 5 つの基本的な状態

Java のスレッドのライフ サイクルに関しては、まず下のより古典的な図を見てください:

Java Thread多线程全面解析

上の図は基本的に Java の多くの側面をカバーしています。糸のポイント。上図の各知識ポイントをマスターすれば、基本的に Java でのマルチスレッドをマスターできます。主に以下が含まれます:

Java スレッドには 5 つの基本的な状態があります

新しい状態 (New): スレッド オブジェクトのペアが作成されると、次のような新しい状態になります。 Thread t = new MyThread();状態 (Runnable): スレッド オブジェクトの start() メソッド (t.start();) が呼び出されると、スレッドは準備完了状態になります。準備完了状態のスレッドは、スレッドが準備ができており、いつでも CPU スケジューリングの実行を待機していることを意味します。 : CPU が準備完了状態のスレッドのスケジュールを開始すると、この時点でスレッドは実際に実行可能になります。つまり、実行状態になります。注: 準備完了状態は、実行状態への唯一の入り口です。つまり、スレッドが実行のために実行状態に入りたい場合は、まず準備完了状態 (ブロック) でなければなりません。何らかの理由で実行状態にあるスレッドは、CPU の使用権を一時的に放棄し、実行を停止するため、準備完了状態になるまでブロック状態になる可能性があります。実行状態になるために CPU によって再度呼び出されます。

ブロックのさまざまな理由に応じて、ブロック状態は 3 つのタイプに分類できます。


1. ブロック待機中: 実行状態のスレッドは wait() メソッドを実行して待機ブロック状態に入ります


2 .同期ブロッキング -- スレッドが同期ロックの取得に失敗した場合 (ロックが他のスレッドによって占有されているため)、スレッドは同期ブロッキング状態に入ります。

3. スレッドのスリープを呼び出すことにより、同期ブロッキング状態になります。 () または join() または I/O リクエストが発行されると、スレッドはブロッキング状態になります。 sleep() 状態がタイムアウトになるか、join() がスレッドの終了を待つかタイムアウトになるか、I/O 処理が完了すると、スレッドは再び準備完了状態に入ります。

デッド状態 (Dead): スレッドは実行を終了したか、例外により run() メソッドを終了し、スレッドはライフサイクルを終了します。

2. Java マルチスレッドの作成と起動

Java でのスレッドの作成は 3 つの基本的な形式に共通しています

1. Thread クラスを継承し、このクラスの run() メソッドをオーバーライドします。

class MyThread extends Thread {
private int i = ;
@Override
public void run() {
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
} 
public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Thread myThread = new MyThread(); // 创建一个新的线程 myThread 此线程进入新建状态
Thread myThread = new MyThread(); // 创建一个新的线程 myThread 此线程进入新建状态
myThread.start(); // 调用start()方法使得线程进入就绪状态
myThread.start(); // 调用start()方法使得线程进入就绪状态
}
}
}
}
ログイン後にコピー

上に示したように、run() メソッドをオーバーライドして、Thread クラスを継承し、新しいスレッド クラス MyThread を定義します。 run() メソッドのメソッド本体は、スレッドと呼ばれる、スレッドが完了する必要があるタスクを表します。実行本体。このスレッド クラス オブジェクトが作成されると、新しいスレッドが作成され、スレッド新規状態になります。スレッドオブジェクトが参照するstart()メソッドを呼び出すことで、スレッドは実行可能状態になります。このとき、CPUのスケジューリングタイミングによっては、すぐにスレッドが実行されない場合があります。


2. Runnable インターフェースを実装し、インターフェースの run() メソッドを書き換えます。 run() メソッドは、Runnable 実装クラスのインスタンスを作成し、このインスタンスを Thread のターゲットとして使用します。クラスを使用して Thread オブジェクトを作成すると、Thread オブジェクトが実際のスレッド オブジェクトになります。

class MyRunnable implements Runnable {
private int i = ;
@Override
public void run() {
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
} 
public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
Thread thread = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
Thread thread = new Thread(myRunnable);
thread.start(); // 调用start()方法使得线程进入就绪状态
thread.start();
}
}
}
}
ログイン後にコピー

新しいスレッドを作成する上記の 2 つの方法は誰もがよく知っていると思いますが、Thread と Runnable の関係は何でしょうか?まずは次の例を見てみましょう。

public class ThreadTest {
public static void main(String[] args) {
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Runnable myRunnable = new MyRunnable();
Thread thread = new MyThread(myRunnable);
thread.start();
}
}
}
}
class MyRunnable implements Runnable {
private int i = ;
@Override
public void run() {
System.out.println("in MyRunnable run");
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class MyThread extends Thread {
private int i = ;
public MyThread(Runnable runnable){
super(runnable);
}
@Override
public void run() {
System.out.println("in MyThread run");
for (i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
ログイン後にコピー

同様に、Runnableインターフェースを実装してスレッドを作成する方法と似ていますが、違いは

Thread thread = new MyThread(myRunnable);
ログイン後にコピー

ということで、この方法でスムーズに新しいスレッドを作成できるでしょうか?答えは「はい」です。この時のスレッドの実行本体は、MyRunnableインターフェースのrun()メソッドなのか、MyThreadクラスのrun()メソッドなのか。出力から、スレッドの実行本体が MyThread クラスの run() メソッドであることがわかります。実際、その理由は非常に単純です。Thread クラス自体も Runnable インターフェイスを実装しており、run() メソッドは最初に Runnable インターフェイスで定義されているからです。

public interface Runnable {
public abstract void run();
}
ログイン後にコピー

Thread クラスの Runnable インターフェイスの run() メソッドの実装を見てみましょう:

  @Override
public void run() {
if (target != null) {
target.run();
}
}
ログイン後にコピー

つまり、Thread クラスの run() メソッドが実行されると、最初に次のことが決定されます。ターゲットが存在するかどうか、存在する場合は実行します。 ターゲットの run() メソッドは、Runnable インターフェイスを実装し、 run() メソッドをオーバーライドするクラスの run() メソッドです。ただし、上記の例では、ポリモーフィズムの存在により、Thread クラスの run() メソッドはまったく実行されませんが、ランタイム型、つまり MyThread クラスの run() メソッドは直接実行されます。最初に実行されました。

3. Callable インターフェイスと Future インターフェイスを使用してスレッドを作成します。具体的には、Callableインターフェースの実装クラスを作成し、clam()メソッドを実装します。そして、FutureTask クラスを使用して Callable 実装クラスのオブジェクトをラップし、この FutureTask オブジェクトを Thread オブジェクトのターゲットとして使用してスレッドを作成します。

少し複雑に思えますが、例を見れば明らかになるでしょう。

public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程for循环执行完毕..");
try {
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = ;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = ;
for (; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
ログイン後にコピー

首先,我们发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target。那么看下FutureTask类的定义:

public class FutureTask<V> implements RunnableFuture<V> {
//....
} 
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
ログイン後にコピー

于是,我们发现FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。

执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢?

原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。

上述主要讲解了三种常见的线程创建方式,对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:不能对同一线程对象两次调用start()方法。

三. Java多线程的就绪、运行和死亡状态

就绪状态转换为运行状态:当此线程得到处理器资源;

运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:

public class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
for (int i = ; i < ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == ) {
thread.start();
}
if(i == ){
myRunnable.stopThread();
}
}
}
}
class MyRunnable implements Runnable {
private boolean stop;
@Override
public void run() {
for (int i = ; i < && !stop; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public void stopThread() {
this.stop = true;
}
}
ログイン後にコピー

以上所述是小编给大家介绍的Java Thread多线程全面解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对PHP中文网的支持!

更多Java Thread多线程全面解析相关文章请关注PHP中文网!


このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

会社のセキュリティソフトウェアはアプリケーションの実行に失敗していますか?それをトラブルシューティングと解決する方法は? 会社のセキュリティソフトウェアはアプリケーションの実行に失敗していますか?それをトラブルシューティングと解決する方法は? Apr 19, 2025 pm 04:51 PM

一部のアプリケーションが適切に機能しないようにする会社のセキュリティソフトウェアのトラブルシューティングとソリューション。多くの企業は、内部ネットワークセキュリティを確保するためにセキュリティソフトウェアを展開します。 ...

MapsTructを使用したシステムドッキングのフィールドマッピングの問題を簡素化する方法は? MapsTructを使用したシステムドッキングのフィールドマッピングの問題を簡素化する方法は? Apr 19, 2025 pm 06:21 PM

システムドッキングでのフィールドマッピング処理は、システムドッキングを実行する際に難しい問題に遭遇することがよくあります。システムのインターフェイスフィールドを効果的にマッピングする方法A ...

エンティティクラス変数名をエレガントに取得して、データベースクエリ条件を構築する方法は? エンティティクラス変数名をエレガントに取得して、データベースクエリ条件を構築する方法は? Apr 19, 2025 pm 11:42 PM

データベース操作にMyBatis-Plusまたはその他のORMフレームワークを使用する場合、エンティティクラスの属性名に基づいてクエリ条件を構築する必要があることがよくあります。あなたが毎回手動で...

Intellijのアイデアは、ログを出力せずにSpring Bootプロジェクトのポート番号をどのように識別しますか? Intellijのアイデアは、ログを出力せずにSpring Bootプロジェクトのポート番号をどのように識別しますか? Apr 19, 2025 pm 11:45 PM

intellijideaultimatiateバージョンを使用してスプリングを開始します...

名前を数値に変換してソートを実装し、グループの一貫性を維持するにはどうすればよいですか? 名前を数値に変換してソートを実装し、グループの一貫性を維持するにはどうすればよいですか? Apr 19, 2025 pm 11:30 PM

多くのアプリケーションシナリオでソートを実装するために名前を数値に変換するソリューションでは、ユーザーはグループ、特に1つでソートする必要がある場合があります...

Javaオブジェクトを配列に安全に変換する方法は? Javaオブジェクトを配列に安全に変換する方法は? Apr 19, 2025 pm 11:33 PM

Javaオブジェクトと配列の変換:リスクの詳細な議論と鋳造タイプ変換の正しい方法多くのJava初心者は、オブジェクトのアレイへの変換に遭遇します...

Redisキャッシュソリューションを使用して、製品ランキングリストの要件を効率的に実現する方法は? Redisキャッシュソリューションを使用して、製品ランキングリストの要件を効率的に実現する方法は? Apr 19, 2025 pm 11:36 PM

Redisキャッシュソリューションは、製品ランキングリストの要件をどのように実現しますか?開発プロセス中に、多くの場合、ランキングの要件に対処する必要があります。

名前を数字に変換してグループ内でソートを実装する方法は? 名前を数字に変換してグループ内でソートを実装する方法は? Apr 19, 2025 pm 01:57 PM

名前を数字に変換してグループ内でソートを実装する方法は?ユーザーをグループでソートする場合、ユーザーの名前を数字に変換して、異なる可能性があることがよくあります...

See all articles