Java マルチスレッドを 1 つの記事で理解する

Javaのマルチスレッド動作の仕組み

Javaマルチスレッドの作成

Java マルチスレッドを作成するには、次の 3 つの方法があります。
1. Thread クラスを継承してプロセスを作成する
2. Runnable インターフェイスを使用してスレッドを作成する
3. Callable インターフェイスおよび Future インターフェイスを使用してスレッドを作成する

1. Threadクラスを継承してプロセスを作成する

  • ステップ 1: サブクラスを作成し、run()クラスのメソッドを書き換え、スレッドの関数を実装する
  • ステップ 2: Thread サブクラスのインスタンスを作成します。つまり、プロセス オブジェクトを作成します。
  • ステップ 3:start()スレッド オブジェクトのメソッドを呼び出してスレッドを開始する

スレッド共通メソッド

方法 説明する
ボイドラン() スレッドの実行中に実行されるコード
無効な中断 割り込み処理
ボイドスタート() スレッドの実行を開始します
静的 void yield() 現在実行中のプロセスを一時停止し、他のプロセスを実行します
Thread.State getState() このスレッドのステータスを返します
最終ブール値 isAlive() スレッドがアクティブかどうかをテストする
文字列 getName() このスレッドの名前を返します
void setName(文字列名) スレッド名を変更する
public class www {
    
    
    public static void main(String[] args){
    
    
        for (int i = 0; i < 3; i++) {
    
    
            Test test = new Test(i);
            test.start();
        }
    }
}
class Test extends Thread{
    
    
    int name;
    public Test(int name){
    
    
        this.name = name;
    }
    public void run() {
    
    
        System.out.println("线程"+name);
    }
}

ここに画像の説明を挿入します

サブスレッドの実行状況は不確実であり、同時に実行されるため、出力結果は毎回異なる場合があります。

欠点: クラスが既にクラスを継承している場合、Threadそのクラスは継承できません。

2. Runnable インターフェイスを介してスレッドを作成する

  • ステップ 1:Runnableインターフェースの実装クラスを定義し、インターフェースのメソッドを実装するrun()
  • ステップ 2:Runnable実装クラスのインスタンスを定義し、このインスタンスをThreadクラスのパラメータとして使用してスレッド オブジェクトtargetを作成します。このオブジェクトが実際のスレッド オブジェクトです。ThreadThread
public class www {
    
    
    public static void main(String[] args){
    
    
        for (int i = 0; i < 3; i++) {
    
    
            Test test = new Test(i);
            Thread t = new Thread(test);
            t.start();
        }
    }
}
class Test implements Runnable{
    
    
    int name;
    public Test(int name){
    
    
        this.name = name;
    }
    public void run() {
    
    
        System.out.println("线程"+name);
    }
}

ここに画像の説明を挿入します

同様に、出力結果が毎回異なる場合がありますが、これもサブスレッドの実行状況が不確実であり、同時に実行されるためです。

3. Callable インターフェイスと Future インターフェイスを介してスレッドを作成する

  • ステップ 1:Callableインターフェースの実装クラスを作成し、call()スレッドの実行本体として機能し、戻り値を持つメソッドを実装します。
  • ステップ 2:Callable実装クラスのインスタンスを作成し、FutureTaskそのクラスを使用して、オブジェクトのメソッドの戻り値をカプセル化するCallableオブジェクトをラップしますFutureTaskCallablecall()
  • ステップ 3:FutureTaskオブジェクトをThreadobjectとして使用してtarget、新しいスレッドを作成して開始します。
  • ステップ 4:サブスレッドの実行終了時にFutureTaskオブジェクトのメソッドを呼び出して戻り値を取得します。get()
public class www {
    
    
    public static void main(String[] args){
    
    
        for (int i = 0 ; i < 3 ; i++){
    
    
            Test test = new Test(i);
            //使用FutureTask来包装Callable对象
            FutureTask result = new FutureTask(test);
            //创建线程对象
            Thread thread = new Thread(result);
            //启动线程
            thread.start();
        }
    }
}
class Test implements Callable{
    
    
    int name;
    public Test(int name){
    
    
        this.name = name;
    }
    @Override
    public Object call() throws Exception {
    
    
        System.out.println("线程"+name);
        return null;
    }
}

ここに画像の説明を挿入します

同様に、出力結果が毎回異なる場合がありますが、これもサブスレッドの実行状況が不確実であり、同時に実行されるためです。

Java スレッドのライフサイクル

具体的には、作成 (New) 状態、実行可能 (Runnable) 状態、ブロック (Blocked) 状態 (Bu Lao Kete)、待機状態 (Waiting) 状態、時間指定待機状態 (Timed wait) の 6 つの状態に分かれています状態、および終了済み (Terminated) 状態。)ステータス。

Javaスレッドのスケジューリング

スレッドスリープ——スリープ

現在実行中のスレッドを一定期間一時停止できます

public class www extends JFrame implements Runnable {
    
    
    JLabel jLabel1,jLabel2;
    public www (){
    
    
        jLabel1 = new JLabel("当前时间:");
        jLabel2 = new JLabel();
        Container containerPane = this.getContentPane();
        containerPane.setLayout(new FlowLayout());
        this.add(jLabel1);
        this.add(jLabel2);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(300,200);
        this.setVisible(true);
    }
    @Override
    public void run() {
    
    
        while (true){
    
    
            jLabel2.setText(getTime());
            //获取当前进程 并延时2000ms
            try {
    
    
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
    String getTime(){
    
    
        Date date =new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss Z");
        return simpleDateFormat.format(date);
    }
    public static void main(String[] args) {
    
    
        www test1 = new www();
        Thread thread1 = new Thread(test1);
        thread1.start();
    }
}

ここに画像の説明を挿入します

2S(2000ms)ごとに時刻が更新されていることがわかります。

スレッドの譲歩 - 譲歩 (youde)

yield()どちらもsleep()現在のプロセスの実行を一時停止します。違いは、yield()プロセスと同じかそれより高い優先度を持つプロセスが存在するかどうかを最初に判断することです。存在する場合は停止し、同じかそれより高い優先度のプロセスを最初に実行します。プロセスが存在しない場合、プロセスは引き続き実行されます。

public class www  implements Runnable {
    
    
    String str = "";
    @Override
    public void run() {
    
    
        for (int i = 1; i <= 9; i++) {
    
    
            str += Thread.currentThread().getName() + "----" + i + "     ";
            if(i%3 == 0){
    
    
                System.out.println(str);
                str = "";
                Thread.currentThread().yield();
            }
        }
    }
    public static void main(String[] args) {
    
    
        www test1 = new www();
        www test2 = new www();
        Thread thread1 = new Thread(test1,"线程1");
        Thread thread2 = new Thread(test2,"线程2");
        thread1.start();
        thread2.start();
    }
}

ここに画像の説明を挿入します

出力結果のスレッドは交互であり、出力結果は交互ではなく実行のたびに異なる場合があるため、yield() でスレッドを制御する実行方法は信頼できません。

スレッドのコラボレーション - 参加

スレッドが特定のポイントに到達し、別のスレッドの終了を待ってから実行を継続する場合、別のスレッドの join() メソッドを呼び出すことでこの状況を実現できます。

public class www {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new test();
        thread.start();
        for (int i = 1; i <= 10; i++) {
    
    
            System.out.println("主线程");
            if(i == 5){
    
    
                try {
    
    
                    thread.join();
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
class test  extends Thread{
    
    
    public void run(){
    
    
        for (int i = 1; i <= 10 ; i++) {
    
    
            System.out.println("子线程");
        }
    }
}

ここに画像の説明を挿入します

通常、メインスレッドがスレッドを作成して起動しますが、サブスレッドの実行に時間がかかると、メインスレッドがサブスレッドよりも先に実行を終了することがよくあります。この例では、サブスレッドを待機する状態に設定します。メインスレッドの実行が終了する前に実行するスレッドjoin()。終了してから実行を継続します。

プロセスの優先順位

Java の各プロセスには対応する優先度があり、優先度が高いプロセスほど実行される機会が多くなります。スレッドの優先度は、通常、1 ~ 10 の数値で表されます。数値が大きいほど、優先度が高くなります。デフォルトのスレッド レベルは 5 です。

Thread クラスには 3 つの定数が定義されています

名前 価値
MIN_PRIORITY 1
MAX_PRIORITY 10
NORM_PRIORITY 5

優先度の設定setPrioriy()方法と優先度の取得方法getPrioriy()

デーモン

Java は 2 つのカテゴリに分類されます用户线程守护线程

デーモン スレッドは、他のスレッドにサービスを提供するバックグラウンド スレッドとも呼ばれ、他のスレッドの実行依存関係や他のスレッドを監視できます。

ユーザースレッドはビジネスロジックを担当する一般的なスレッドです

このメソッドを使用してsetDaemon()、スレッドのタイプを true に設定できます。デーモン スレッドは线程.start()前にのみ呼び出すことができます。また、isDaemon()このメソッドを使用して、プロセスがデーモン プロセスかどうかを判断することもできます。

スレッドの同期

マルチスレッドによって引き起こされる問題: マルチスレッドを設計する場合、クラスのプライベート メンバー変数または静的メンバーを共有するという目的を達成するために、複数のスレッドがコード ブロックの一部を共有する必要がある場合があります。このとき、スレッド間で CPU リソースが競合するため、スレッドがこれらの共有リソースにアクセスする順序が狂い、最終的に正しい結果が得られない場合があり、このような問題は通常スレッドセーフ問題と呼ばれます。

例えば:

public class www {
    
    
    public static void main(String[] args) {
    
    
        test test = new test();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
    
    
    public int num = 0;
    //使用temp是为了增加线程的切换几率
    private void add(){
    
    
        int temp;
        for (int i = 0; i < 1000; i++) {
    
    
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
    
    
        add();
    }
}

ここに画像の説明を挿入します

出力結果が 1000 の倍数で正しくならないことはほとんどありません。これは、マルチスレッドの同時実行と複数のスレッドによる変数の同時変更が原因でありnum、この問題を解決するには Java の同期機構が必要です。

同期されたコードブロック

Java には、インスタンス オブジェクトまたはクラス オブジェクトの各オブジェクトに対してロックと待機セットが備わっています。インスタンス オブジェクトをロックすると、インスタンス オブジェクトに関連するスレッドがオブジェクト ロックを相互に排他的に使用できるようになり、クラス オブジェクトをロックすると、クラスに関連するスレッドがクラス オブジェクト ロックを相互に使用できるようになります。new キーワードを使用してインスタンス オブジェクトを作成し、オブジェクトへの参照を取得します。クラス オブジェクトへの参照を取得します。メンバーメソッドを渡すことができますforNameクラスの静的メンバー変数と静的メンバー メソッドはクラス オブジェクトに属し、クラスの非静的メンバー変数と非静的メンバー メソッドはクラスのインスタンス オブジェクトに属します。

synchronize(synObject){
    
    
    //关键code
}

プロセスがクリティカル コードに入ると、システムはまずオブジェクトのロックが他のスレッドによって取得されているかどうかを確認します。取得されていない場合、JVM は現在ロックを要求しているプロセスにオブジェクトのロックを渡します。スレッドがロックを取得した後、重要なコード領域に入る可能性があります。

例えば:

public class www {
    
    
    public static void main(String[] args) {
    
    
        test test = new test();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
    
    
    int num = 0;
    private void add(){
    
    
        int temp;
        for (int i = 0; i < 1000; i++) {
    
    
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
    
    
        synchronized (this){
    
    
            add();
        }
    }
}

ここに画像の説明を挿入します

このようにして、各スレッドの実行結果は整数になります。

同じ結果を得るには、add() メソッドを synchronized に変更します。これは、以下で説明する同期メソッドです。

private synchronized void add(){
    
    
        int temp;
        for (int i = 0; i < 1000; i++) {
    
    
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }

同期メソッド

同期コード ブロックなどの同期メソッドは、インターロックを使用してコード同期アクセスを実現します。

public class www {
    
    
    public static void main(String[] args) {
    
    
        test test = new test();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
    
    
    int num = 0;
    private synchronized void add(){
    
    
        int temp;
        for (int i = 0; i < 1000; i++) {
    
    
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
    
    
            add();
    }
}

ここに画像の説明を挿入します

同期は高コストの操作であるため、大規模な設定で同期メソッドを使用することは避けsynchronize、通常はsynchronizeコード ブロックを使用してキー コードを同期します。

スレッド通信

マルチスレッドでは実行中に順序の問題が発生することがありますが、Java にはスレッド通信の問題を解決する 3 つの方法が用意されています。これらはそれぞれwait()notify()、 、メソッドですnotifyAll()これら 3 つのキーワードは、synchronized キーワードの範囲内でのみ機能し、同じメソッド内でこれら 3 つのメソッドと組み合わせた場合にのみ実際的な意味を持ちます。

wait()メソッド: このメソッドを呼び出したスレッドは、共有リソースのロックを解放し、実行可能状態から待機状態に移行できます。再び目覚めることを知ってください。

notify()方法: 同じ共有リソースを待っている待機キュー内の最初のスレッドをウェイクアップし、プロセスを待機状態から抜け出して実行可能状態にします。

notifyAll()方法: 待機キュー内の同じ共有リソースを待機しているすべてのプロセスは、待機状態を抜けて実行可能状態に移行できます。複数のプロセスがある場合は、優先度の高いプロセスが最初に実行されます。どのプロセスを起動すればよいかわからない場合は、この方法を使用します。
補足知識:
スレッドの5つの状態について詳しく解説

以下に例を示します。

public class www {
    
    
    public static void main(String[] args) {
    
    
        //实例化一个ShareStore对象 并创建进程启动
        ShareStore shareStore = new ShareStore();
        new Consumer(shareStore).start();
        new Producer(shareStore).start();
    }
}

/***
 * 生产者
 */
class Producer extends Thread{
    
    
    private ShareStore shareStore;
    Producer(ShareStore shareStore){
    
    
        this.shareStore = shareStore;
    }
    @Override
    public void run() {
    
    
        int num = 1;
        while (true){
    
    
            shareStore.setShareNum(++num);
            System.out.println("Producer生产了一个数字"+num);
            //睡眠1s
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
}
/***
 * 消费者
 */
class Consumer extends Thread{
    
    
    private ShareStore shareStore;
    Consumer(ShareStore shareStore){
    
    
        this.shareStore = shareStore;
    }
    @Override
    public void run() {
    
    
        int num = 1;
        while (true){
    
    
            num = shareStore.getShareNum();
            System.out.println("Consumer消费了一个数字"+num);
            //睡眠1s
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
}

/***
 * 用来管理的
 */
class ShareStore{
    
    
    private int num;
    private boolean writeable = true;
    public synchronized void setShareNum(int num){
    
    
        if (!writeable){
    
    
            try {
    
    
                wait();//等待消费者消费完成
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
        this.num = num;
        writeable = false;
        notify(); //通知消费者 生产者已经生产可以消费
    }
    public synchronized int getShareNum(){
    
    
        if (writeable){
    
    
            try {
    
    
                wait(); //等待生产者生产出来
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
        writeable = true;
        notify();//通知可以生产
        return this.num;
    }
}

ここに画像の説明を挿入します

出力結果は、プロデューサーが最初に数値を生成し、次にコンシューマーがそれを消費します。

デッドロック

デッドロックとは、2 つ以上のプロセスが単一待機リングを形成する場合、各プロセスが互いのリソースの解放を待機し、どちらも実行できずにデッドロックが形成され続けるシナリオです。

おすすめ

転載: blog.csdn.net/Systemmax20/article/details/125576353