Java マルチスレッドの基礎 | JUC 同時プログラミングの基礎

1. プロセスとスレッドの基本概念

プロセスやスレッドは最初から存在するのではなく、必要に応じて現れる概念です。

1. プロセスの背景

コンピュータは最初は特定の命令しか受け付けませんでしたが、ユーザーが命令を入力するとコンピュータは動作を実行します。しかし、ユーザー入力の速度はコンピューターの計算速度よりもはるかに遅いため、コンピューターはユーザー入力の待機に多くの時間を費やします。つまり、CPU は常にアイドル状態になり、CPU の使用率は低くなります。とても低い。

バッチオペレーティングシステム

その後、いくつかの一連の命令をリストに書き込み、そのリストを一度にコンピュータに与えるバッチ オペレーティング システムが登場しました。その後、コンピュータは命令を 1 行ずつ読み取って、結果を別のディスクに出力しました。

バッチ オペレーティング システムの登場によりコンピュータの効率は向上しましたが、バッチ オペレーティング システムのコマンド動作モードは依然としてシリアルであるため、メモリ内で常に 1 つのプログラムだけが実行されますこの種のバッチ処理オペレーティング システムは、CPU が 1 つのプログラムの命令しか実行できないため、理想的ではありません。つまり、メモリ内で実行できるプログラムは 1 つだけです。

プロセスのご提案

メモリ内で実行できるプログラムは 1 つだけであるため、コンピュータ科学者はプロセスという概念を思いつきました。

プロセスとは、アプリケーション プログラムによってメモリ内で割り当てられる領域です。つまり、複数のプログラムがメモリ内で同時に実行でき、各アプリケーション プログラム (プロセス) は互いに干渉しません。各プロセスは、プログラムの実行状態を保存します。

プログラム: 特定の機能を実現できるコードの集合を指します。

コンピュータの CPU は、各プロセスをタイム スライスで実行します。CPU は各プロセスにタイム スライスを割り当てますが、タイム スライスが終了したときにプロセスがまだ実行中の場合、プロセスは一時停止され、CPU が別のプロセスに割り当てられます (このプロセスはコンテキスト スイッチングです)。タイム クォンタムが期限切れになる前にプロセスがブロックまたは終了した場合、CPU はタイム クォンタムが切れるのを待たずにすぐに切り替わります。

CPU コンテキストの切り替えは非常に時間がかかる操作であることに注意してください。これは、プロセスを切り替える前に現在のプロセスの状態 (プロセス ID、プロセスが使用するリソースなど) を保存する必要があるためです。以前に保存された内容に従って次回 CPU タイム スライスを取得します。状態が復元され、実行が続行されます。

CPUタイムスライス+プロセスという手法を使うと、巨視的には同じ時間内に複数のタスクが実行されているように感じられますが、これはCPUの計算速度が速すぎるため、タスクを同時処理しているように見えるからです。実際、シングルコア CPU の場合、常に 1 つのプログラムだけが実行されます。

同時実行性: 複数のタスクを同時に処理します。

並列処理: 複数のタスクを同時に処理します。

オペレーティング システム要件のさらなる増加

プロセスの登場によりオペレーティング システムのパフォーマンスは大幅に向上しましたが、時間が経つにつれて、プロセスが一定期間に 1 つのことしか実行できないことに人々は満足しなくなりました。これらのサブタスクは効率に大きく影響します。

たとえば、コンピューターのウイルス対策ソフトウェアでは、ウイルスのスキャン中にジャンクをスキャンすることはできません。ジャンクをスキャンするには、ウイルス スキャンが完了するまで待つ必要があります。

スレッドのプレゼンテーション

これらのサブタスクを同時に実行できますか? そこでスレッドの概念が提唱されました。スレッドはプロセスよりも小さい単位であり、プログラムはプロセスであり、プロセスには 1 つ以上のスレッドを含めることができます。

画像-20230405200232690

たとえば、コンピュータのウイルス対策ソフトウェアは、ウイルスとゴミを同時にスキャンできます。

プロセスはオペレーティング システムの同時実行を可能にし、スレッドはプロセスの内部同時実行を可能にします。

プロセスでも同時実行を実現できるのに、なぜプロセスを提案する必要があるのでしょうか?

  • プロセス通信はより複雑で、データの共有は容易ではなく、異なるプロセス間にはメモリの壁があります。スレッドにより、データの共有とスレッド間の通信が容易になります。
  • プロセスが重く、スイッチングプロセスのオーバーヘッドが比較的高く、レジスタやスタック情報の保存だけでなく、リソースの割り当てやリサイクル、ページングも必要です。スレッドはレジスタとスタック情報を保存するだけでよく、オーバーヘッドは比較的小さいです。

2、マルチスレッド化

上記では、プロセスとスレッドの理由について説明しました。では、Java でスレッドを作成するにはどうすればよいでしょうか?

1. Threadクラスを継承する

class MyThread extends Thread {
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
}
public class Demo04 {
    
    
    public static void main(String[] args) {
    
    
        MyThread thread1 = new MyThread();
        thread1.setName("子线程1");
        thread1.start();
        MyThread thread2 = new MyThread();
        thread2.setName("子线程2");
        thread2.start();
        System.out.println(Thread.currentThread().getName());
    }
}

注: 同じスレッドでstart()メソッドを複数回呼び出すことはできません。呼び出すと、IllegalThreadStateException例外。

start() ソースコード:

private volatile int threadStatus = 0;
public synchronized void start() {
    
    
    /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
    
    
        start0();
        started = true;
    } finally {
    
    
        try {
    
    
            if (!started) {
    
    
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
    
    
            /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
        }
    }
}

private native void start0();

ソースコードを解析したところ、threadStatus = 0ローカルメソッド呼び出しstart0()後に変更さthreadStatusれる値が変更されることが判明したため、2回目のstart()メソッド呼び出し時に例外が報告されますIllegalThreadStateException

2. Runnableインターフェイスを実装する

スレッドは上記の Thread クラスを継承して作成されますが、Java では単一継承と多重実装であることがわかります。クラスが Thread クラスを継承する場合、他のクラスを明示的に継承することはできません。新しいスレッド タスクを作成して他のクラスを継承したい場合、どうすれば実現できますか?

Thread のコンストラクターでは、Runnable インターフェースの実装クラスを渡してスレッドを作成することがサポートされています。

画像-20230405203241447

class MyCustomThread implements Runnable {
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
}
public class Demo05 {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(new MyCustomThread());
        thread.start();
    }
}

また、匿名の内部クラス オブジェクトとして記述したり、lamdab 式として記述したりするなど、上記のコードを簡略化することもできます。

匿名内部クラス オブジェクトとは何ですか?

匿名内部クラスは、名前のない内部クラスを指します。このクラスは、宣言時にインターフェイスを直接実装するかクラスを継承し、使用時に直接作成およびインスタンス化されます。匿名内部クラスは Java で非常に一般的に使用されており、コードの作成を簡素化し、コードをより簡潔にし、プログラマがコードの実装の詳細をある程度隠すこともできます。

フォーマット:

新しい親クラスのコンストラクター <実際のパラメーター リスト> は、インターフェイス名 <汎用​​パラメーター リスト> を実装します。 {外部クラス メンバー変数、メソッド; [内部クラス メンバー変数] [内部クラス メソッド]}

JDK 8 以降では、外部変数を変更しない場合は、最終的な変更を行わずに直接アクセスできます。

public class OuterClass {
     
     
    public void myMethod() {
     
     
        final int x = 3; // 将x声明为final
        new Thread(new Runnable() {
     
     
            @Override
            public void run() {
     
     
                x++; // 编译错误,无法修改x的值
                System.out.println(x);
            }
        }).start();
    }
}

匿名の内部クラス オブジェクト:

public class Demo05 {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(new Runnable {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName());
            }
        });
        thread.start();
    }
}

匿名オブジェクト、匿名内部クラス オブジェクト、内部クラスを区別するため。

匿名オブジェクト: 作成されたオブジェクトには名前が付けられません。

例: new MyThread()

内部クラス: クラス内で定義されたクラス。

例えば:

class OutterClass {
     
     
    // 外部类成员
    class InnerClass {
     
     
        // 内部类成员
    }
}

匿名の内部クラス オブジェクト: 内部クラスのインスタンスには名前が付けられません。

コードをさらに簡略化し、ラムダブ式として記述することができます。

public class Demo05 {
    
    
    public static void main(String[] args) {
    
    
        new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
    }
}

lamdab に慣れていない場合は、オンラインで検索して学ぶことができます。

lamdab を使用するには、使用する JDK バージョンが 8 以上である必要があります。


さらに詳しい内容については、私のブログをご覧ください

おすすめ

転載: blog.csdn.net/qq_43907505/article/details/130054237