Java並行プログラミング(Ⅰ)


1.コンセプト

1. 基本的な考え方

プロセスとスレッド:

  • グラフィック:
  • 文字:
    • プロセス: プログラムのインスタンス。下の図に示すように、プログラムは複数のインスタンスを持つことができます。

      現在、QQ プログラムには 2 つのプロセスがあります。
    • スレッド: スレッドはプロセスの実行単位であり、プロセス内には複数のスレッドが存在します。
  • コンセプト:
    • プロセス: プロセスはリソースの割り当てと管理の基本単位であり、さまざまなプロセスがコンピューター システム リソースをめぐって競合します。
    • スレッド: スレッドは最小のスケジューリング単位(プロセッサー スケジューリングの基本単位)です。
  • 違い:
    • 独立 | 共有
      • スレッドはプロセスのアドレス空間とリソース (メモリ、CPU、IO など) を共有します。
      • 各プロセスには独立したアドレス空間と独立したリソースがあります。
    • 屈強?
      • 1 つのスレッドがクラッシュすると、プロセス全体がクラッシュします
      • プロセスがクラッシュしても、保護モードの他のプロセスには影響しません。
    • オーバーヘッド?
      • スレッドは実行するプロセスに接続されており、明らかにプロセスのオーバーヘッドが高くなります (起動オーバーヘッド、スイッチング オーバーヘッドを含む)。
    • 通信
      • 同じコンピュータ間のプロセス通信はIPC (プロセス間通信)と呼ばれ、異なるコンピュータ間のプロセス通信はネットワークを経由し、HTTPなどの共通プロトコルに従う必要があります。
      • スレッドはプロセス リソースを共有するため、静的リソースの共有など、通信が非常に簡単になります。

同時実行性と並列性:
シングルコア CPU は、一度に 1 つのスレッドのみを実行できます。つまり、シリアル実行です。オペレーティング システムのタスク スケジューラは、 CPU タイム スライス (非常に短い) をさまざまなスレッドに割り当てます。CPU はスレッド間を非常に素早く切り替えるため、複数のスレッドが同時に実行されているように錯覚します。

  • 並行性:複数のことを同時に処理する能力
  • 並列:複数のことを同時に実行できる機能

同期と非同期:

  • 同期:つまり、シリアル実行では、前のコードが返されるまで待ってから、後続のコードの実行を続行する必要があります。

    @Slf4j
    public class ShowSync {
          
          
    
        public static void count(int i) {
          
          
            log.info("第" + i + "次执行count方法");
        }
    
        public static void main(String[] args) {
          
          
            for (int i = 0; i < 3; i++) {
          
          
                count(i);
            }
            log.info("线程:" + Thread.currentThread().getName());
        }
    }
    
  • 非同期:次のコードは、前のコードが返されるのを待たずに実行できます。

    @Slf4j
    public class ShowAsync {
          
          
        public static void main(String[] args) {
          
          
            for (int i = 0; i < 3; i++) {
          
          
                new Thread(() ->
                    log.info("线程:" + Thread.currentThread().getName())
                ).start();
            }
            log.info("线程:" + Thread.currentThread().getName());
        }
    }
    

2. スレッドの状態

次のように、列挙型クラスStateがThreadクラスで提供されます

public enum State {
    
    

        NEW,
        
        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        TERMINATED;
    }

Java スレッドには6 つの状態があることがわかりますしかし実際には、以下の図に示すように、 RUNNABLE をReadyRUNNABLE の2 つの状態に分割するため、一般的に「 Java スレッドには7 つの状態がある」と言えます。

  • 新しい
    • Thread t1 = new Thread() がこの状態になります
  • 準備
    • t.start() はこの状態に入ります
    • この時点で、スレッドはすでに実行できますが、CPU タイム スライスをまだ取得しておらず、準備完了状態になっています。
  • ランニング
    • 準備完了スレッドは CPU タイム スライスを取得し、実行状態に入ります。
  • ブロックされました
    • スレッドが同期された変更されたメソッドまたはコード ブロックに入る
  • 終了しました
    • スレッドのrun() の実行が終了したか、main()の実行が終了しました
  • 待っている
    • この状態になるスレッドには CPU タイム スライスが割り当てられないため、他のスレッドによってウェイクアップされる必要があります。そうしないと、永遠に待機することになります。
  • TIMED_WAITING
    • この状態になるスレッドには CPU タイム スライスが割り当てられず、他のスレッドによってウェイクアップされる必要がなく、一定時間が経過すると自動的にウェイクアップされます。

スレッド内の状態遷移や関連するメソッドについては、後ほど紹介します。

2. スレッドの初期化

スレッドの初期化、つまりnew Thread()では、初期化を実現するには 3 つの方法があります。

  • new Thread() を指示してからrun()を書き換えます
  • new Thread(new Runnable())Runnablerun()をThreadアセンブルします
  • new Thread(new FutureTask(new Callable()))Callable のcall()を最初にFutureTaskにアセンブルし、次にThreadにアセンブルします。

1. 新しいスレッド()

スレッドを初期化する直接new Thread()は、スレッド (Thread) とタスク (run()) をバインドすることになります。実際の開発での使用は推奨されません。コードは次のとおりです。

@Slf4j
public class JustThread {
    
    
    public static void main(String[] args) {
    
    
        Thread thread1 = new Thread("线程1") {
    
    
            @Override
            public void run() {
    
    
                log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
            }
        };
        thread1.start();

        log.info("线程:" + Thread.currentThread().getName() + " 运行中...");

        /*
            lambda表达式
         */
        Thread thread2 = new Thread(() -> {
    
    
            log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
        }, "线程2");
        thread2.start();
    }
}

2.新しいスレッド(新しいRunnable())

スレッド (Thread) とタスク (Runnable) を個別に初期化し、それらを結合します。

@Slf4j
public class RunnableAndThread {
    
    
    public static void main(String[] args) {
    
    
        Runnable runnable = new Runnable() {
    
    
            @Override
            public void run() {
    
    
                log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
            }
        };
        Thread thread = new Thread(runnable, "线程3");
        thread.start();

        /*
            lambda表达式
         */
        Runnable runnable1 = () -> log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
        Thread thread1 = new Thread(runnable1, "线程4");
        thread1.start();
    }
}

3. スレッドとランナブルの関係

まず、次のようにRunnableのソース コードを確認します

@FunctionalInterface
public interface Runnable {
    
    
    public abstract void run();
}

run()が 1 つだけある関数型インターフェイス

次に、Threadのソース コードの一部を見てみましょう。

    @Override
    public void run() {
    
    
        if (target != null) {
    
    
            target.run();
        }
    }

    public Thread(Runnable target) {
    
    
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
    
    
        init(g, target, name, stackSize, null, true);
    }

さて、これらのメソッドのいくつかの段落を見てください。ここでは詳細な分析は行いません。上記のコードから、ThreadRunnableの関係を明確に理解できます。

  • 新しい ThreadのときにRunnableが渡されない場合Threadrun()が書き換えられます
  • 新しい ThreadRunnableが渡されると、 Runnablerun()が使用されます。

したがって、RunnableインターフェースはThreadメソッド本体run()を提供します。

実際の開発では、通常、 Runnable を実装するようにクラスをカスタマイズし、このクラスをThreadに渡します。ケースは次のとおりです。

  • 現在時刻を 10 秒ごとに出力するスレッドを定義する
public class TimePrinter implements Runnable{
    
    
    private SimpleDateFormat dateFormat= new SimpleDateFormat("hh:mm:ss");

    @Override
    public void run() {
    
    
        while (true) {
    
    
            try {
    
    
                System.out.println("当前时间为: " + dateFormat.format(new Date()));
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
}
public class TaskDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(new TimePrinter(), "测试线程");
        thread.start();
    }
}

4.新しいスレッド(新しいFutureTask(新しいCallable()))

@Slf4j
public class FutureTaskAndThread {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    


        FutureTask<Long> futureTask = new FutureTask<>(new Callable<Long>() {
    
    
            @Override
            public Long call() throws Exception {
    
    
                long start = System.currentTimeMillis();
                log.info(Thread.currentThread().getName() + " 运行中...");
                TimeUnit.MILLISECONDS.sleep(123);
                long end = System.currentTimeMillis();
                return end - start;
            }
        });
        Thread thread = new Thread(futureTask, "线程5");
        thread.start();
        log.info(thread.getName() + "花费了 " + futureTask.get() + " 毫秒");
    }
}

Runnable と Callable の関係は次のとおりです。Runnablerun()と比較すると、Callablecall()には戻り値が 1 つ多いこと
がわかります。

@FunctionalInterface
public interface Callable<V> {
    
    
    V call() throws Exception;
}

3. 一般的な方法

スレッド状態の切り替え:

TIMED_WAITING と WAITING の違い:

  • TIMED_WAITING 外部シグナルがない場合でも、待ち時間が経過するとスレッドが再開されます。
  • WAITING は外部信号がウェイクアップするまで待つ必要があります

1.開始

まずソースコードを見てください。

	// 线程组
	private ThreadGroup group;
	// NEW 状态的线程的 threadStatus = 0
	private volatile int threadStatus = 0;

    public synchronized void start() {
    
    

        if (threadStatus != 0)
            throw new IllegalThreadStateException();
		// 将要启动(start)的线程装入线程组中
        group.add(this);

        boolean started = false;
        try {
    
    
            start0();
            started = true;
        } finally {
    
    
            try {
    
    
                if (!started) {
    
    
                	// 告诉组,这个线程启动(start)失败
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
    
    

            }
        }
    }

	// native方法,调用本地操作系统开启一个新的线程
    private native void start0();

ソース コードを読むと、次の結論が得られます。

  • startスレッドを開始するには、次の手順を実行する必要があります。
    1. startを呼び出すスレッドをスレッド グループThreadGroupにロードします。
    2. ネイティブ メソッドを呼び出してスレッドを開始する

1. スレッドグループ

参考リンクはこちら: https: //zhuanlan.zhihu.com/p/61331974

スレッド グループ (スレッド グループ):グループ内のスレッドを簡単に管理できるスレッドの集合。

スレッドグループツリー:

  • システム スレッド グループ: オブジェクトの破棄などの JVM システム タスクを処理するスレッド グループ。

  • メインスレッドグループ: 少なくとも 1 つのスレッド - main (main メソッドの実行に使用) が含まれます。

  • フェイススレッドグループの子スレッドグループ:アプリケーションによって作成されたスレッドグループ

        public static void main(String[] args) {
          
          
            // 输出当前线程组——main线程组
            System.out.println(Thread.currentThread().getThreadGroup().getName());
            // 输出当前线程组的父线程组——System
            System.out.println(Thread.currentThread().getThreadGroup().getParent().getName());
        }
    

スレッド グループをスレッドに割り当てます。

  • スレッドの初期化時に配列を指定しない場合、デフォルトでメイン スレッド グループとして指定されます。コードは次のとおりです。

    private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
          
          
                          
            /*
    			其余代码省略
    		*/
            Thread parent = currentThread();
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
          
          
              
                if (security != null) {
          
          
                    g = security.getThreadGroup();
                }
    
                if (g == null) {
          
          
                    g = parent.getThreadGroup();
                }
            }
    
            /*
    			其余代码省略
    		*/
    }
    
  • スレッド グループは、次のコンストラクターのいずれかを使用してスレッドに割り当てることができます。

        public Thread(ThreadGroup group, Runnable target)
    
        public Thread(ThreadGroup group, Runnable target) 
    
    	public Thread(ThreadGroup group, Runnable target, String name)
    
        public Thread(ThreadGroup group, Runnable target, String name, long stackSize)
    

    それを実証してみましょう:

        public static void main(String[] args) {
          
          
            ThreadGroup group = new ThreadGroup("我的线程组");
            Thread thread = new Thread(group, "我的线程");
            System.out.println(thread.getName() + " 的线程组是 " + group.getName());
        }
    

スレッドグループメソッド分析: https://blog.csdn.net/a1064072510/article/details/87455525

要約すると、スレッドをスレッド グループにロードする目的は、スレッドのバッチ操作を容易にすることです。たとえば、複数のスレッドに終了が近づいていることを通知したい場合、これらのスレッドを事前にスレッド グループに入れることができます。次に、スレッド グループを通じてバッチで直接通知します。

// 通知main线程组中的所有线程要终止了
Thread.currentThread().getThreadGroup().interrupt();

2. 起動して実行する

質問: startはスレッドを開始できるのに、実行できないのはなぜですか?

回答: Java はオペレーティング システムに直接作用できないため、ネイティブ メソッド (C、C++ などで書かれたメソッドなど) を呼び出して、オペレーティング システムに新しいスレッドを開始するように指示する必要があります。startメソッドでは、ネイティブ メソッド start0が呼び出されてスレッドが開始されます。runはスレッド内のメソッドであり、スレッドが実行されるとrunが実行され、runメソッドが実行されるとスレッドが終了します。

2.睡眠

まずThread.sleepのソース コードを見てください。

	/**
	*让当前线程睡眠指定毫秒数,线程不会丢失任何monitors的所有权
	*/
    public static native void sleep(long millis) throws InterruptedException;

さて、それでは使ってみましょう:

@Slf4j
public class Sleep_md{
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread(()->{
    
    
            long start = System.currentTimeMillis();
            log.info("t1睡眠5s");
            try {
    
    
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
    
    
            }
            long end = System.currentTimeMillis();
            log.info("已过去 {} 毫秒", (end - start));
        });
        t1.start();
    }
}

上記のコードの操作を通じて、 Thread.sleep は現在のスレッドを一定期間TIMED_WAITINGスリープ状態にし、その後RUNNABLE状態に移行させることができることがわかりますこの期間中、CPU は他のスレッドにチャンスを与えるために解放されます。

1. 使用時間

Thread.sleepに加えてTimeUtil にはスレッドをスリープ状態にできるsleepもあり、どちらも同じ効果があります。

    public void sleep(long timeout) throws InterruptedException {
    
    
        if (timeout > 0) {
    
    
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }

手順:

// 睡眠n纳秒
TimeUtil.NANOSECONDS.sleep(long n)
// 睡眠n微秒
TimeUtil.MICROSECONDS.sleep(long n)
// 睡眠n毫秒
TimeUtil.MILLISECONDS.sleep(long n)
// 睡眠n秒
TimeUtil.SECONDS.sleep(long n)
// 睡眠n分钟
TimeUtil.MINUTES.sleep(long n)
// 睡眠n小时
TimeUtil.HOURS.sleep(long n)
// 睡眠n天
TimeUtil.DAYS.sleep(long n)

2. 中断例外

InterruptedExceptionは、スリープ状態のスレッドがスリープ状態から中断されるとスローされます

@Slf4j
public class Sleep_md{
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(() -> {
    
    
            log.info("开始睡眠");
            long start = System.currentTimeMillis();
            try {
    
    
            	// 睡眠 60s
                Thread.sleep(1000 * 60);
            } catch (InterruptedException e) {
    
    
                long end = System.currentTimeMillis();
                log.info("中途停止睡眠");
            }
        });
        thread.start();
        thread.interrupt();
    }
}

肉眼で見えるように、interrupt() はスリープを中断し、例外をスローし、キャッチされます。

3.優先度の設定

setPriority() —— スレッドの優先順位を設定します。値は 1 ~ 10 で、値が小さいほど優先順位が高くなります。
注:優先順位が高いということは、CPU タイム スライスをめぐって競合する機会が増えることを示しており、優先順位が高いほど高速に実行する必要があるということではありません。

  • CPU がビジーな場合、優先度の高いスレッドがより多くのタイム スライスを取得します。
  • CPU がアイドル状態の場合、優先度はほとんど影響しません。
@Slf4j
public class Priority_md {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> log.info("t1运行中..."), "t1");
        t1.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        Thread t2 = new Thread(() -> log.info("t2运行中..."), "t2");
        t2.setPriority(Thread.MIN_PRIORITY);
        t2.start();
        log.info("main运行中...");
    }
}
  • 3つの定数

        public final static int MIN_PRIORITY = 1;
        public final static int NORM_PRIORITY = 5;
        public final static int MAX_PRIORITY = 10;
    
  • 優先順位を設定するときに、値が [1,10] の範囲にない場合、例外IllegalArgumentExceptionがスローされます。

  • メインスレッドの優先度は5で、カスタムスレッドの優先度の初期値も5です。

    Thread main = Thread.currentThread();
    log.info("main线程的优先级是: " + main.getPriority());
    

4.収量

Thread.yield() —— 現在のスレッドが準備完了状態に戻るために CPU タイム スライスを放棄する意思があることをスレッド スケジューラにプロンプ​​トし、スレッド スケジューラはプロンプトを無視することを選択できます (放棄できない場合があります)。

知らせ:

  • タイム スライスを放棄しても必ずしも成功するとは限りません。すべてはスレッド スケジューラに依存します。
  • 放棄されたタイム スライスは、高優先度のスレッドによって取得されない可能性があります。CPU が緊張していない場合、優先度は役に立たないためです。たとえ CPU が比較的逼迫していても、高優先度のスレッドの取得能力が向上するだけです。タイムスライス。
@Slf4j
public class Yield_md {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(new Runner(), "张三");
        t1.setPriority(Thread.MIN_PRIORITY);
        Thread t2 = new Thread(new Runner(), "李四");
        t2.setPriority(Thread.MIN_PRIORITY);
        Thread t3 = new Thread(new Runner(), "王五");
        t3.setPriority(Thread.MIN_PRIORITY);

        for (int i = 1; i < 10; i++) {
    
    
            Thread thread = new Thread(new Runner(), "路人" + i);
            thread.setPriority(Thread.MAX_PRIORITY);
            thread.start();
        }

        t1.start();
        t2.start();
        t3.start();
    }

    private static class Runner implements Runnable {
    
    

        @Override
        public void run() {
    
    
            for (int i = 1; i < 8; i++) {
    
    
                if (i % 4 == 0) {
    
    
                    log.info(Thread.currentThread().getName() + " 摔了一跤!");
                    Thread.yield();
                }
                if (i == 7) {
    
    
                    log.info(Thread.currentThread().getName() + " 完成了比赛!");
                }
            }
        }
    }
}

歩留まりと睡眠:

  • イールドが成功すると、スレッドは直接READYに入り、スリープはWAITINGまたはTIMED_WAITINGに入ってから入りますしたがって、yieldのスレッドはいつでも CPU を取り戻して実行を継続できますが、スリープはスリープ時間の終了を待つ必要があります。

5. 参加する

join()は、指定されたスレッド A を現在実行中のスレッド B に結合し、スレッド B は待機状態に入り、A の実行が終了するのを待ってから B を実行します。

@Slf4j
public class Join_md {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread(() -> {
    
    
            try {
    
    
                for (int i = 0; i < 3; i++) {
    
    
                    log.info("t1----->" + i);
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            try {
    
    
                t1.join();
                for (int i = 0; i < 3; i++) {
    
    
                    log.info("t2----->" + i);
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }, "t2");
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();

        try {
    
    
            t2.join();
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        log.debug("t1: {} t2: {} cost: {}", t1, t2, end - start);
    }
}

知らせ:

  • 結合は、 2 つの代替実行スレッドを順番に実行することと同じです。

  • 複数のスレッド (A、B、C など)がスレッド D に参加すると、次のように A、B、C が交互に実行されます。

    @Slf4j
    public class Join_md {
          
          
        public static void main(String[] args) throws InterruptedException {
          
          
            Thread t1 = new Thread(() -> {
          
          
                try {
          
          
                    log.info("t1......");
                    for (int i = 0; i < 5; i++) {
          
          
                        log.info("t1: " + i);
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
          
          
                    throw new RuntimeException(e);
                }
            }, "t1");
            Thread t2 = new Thread(() -> {
          
          
                try {
          
          
                    log.info("t2......");
                    for (int i = 0; i < 5; i++) {
          
          
                        log.info("t2: " + i);
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
          
          
                    throw new RuntimeException(e);
                }
            }, "t2");
            long start = System.currentTimeMillis();
            t1.start();
            t2.start();
    
            log.info("main...");
    
            try {
          
          
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
          
          
                throw new RuntimeException(e);
            }
            long end = System.currentTimeMillis();
            log.debug("t1: {} t2: {} cost: {}", t1, t2, end - start);
        }
    }
    

結合と生成:

  • 関数の観点から: join は現在のスレッドをブロックし、他のスレッドを最初に実行させます。yield現在のスレッドにその位置を放棄させ、他のスレッドを実行させます。
  • ステータスの観点から: join では現在のスレッドがWAITINGに入りyieldでは現在のスレッドがREADYになります。
  • 効果の観点から:結合は失敗しませんが、yield ではリソースが失敗する可能性があります

6.中断

1. 手法の紹介

割り込みは、スレッドの割り込みステータスを変更し (割り込みフラグを設定するだけ)、そのスレッドに通知するだけであり、実行中のスレッドには割り込みません。

isInterrupted は、現在のスレッドの割り込みステータスを決定します。

Thread.interruptedによって呼び出される現在のスレッドのisInterrupted は、現在のスレッドの割り込みステータスを true にリセットします。
割り込みの後、スレッド isInterrupted は true を返し、Thread.interrupted は状態を false にリセットします。

@Slf4j
public class Interrupt_md {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread(() -> {
    
    
            if (Thread.currentThread().isInterrupted()) {
    
    
                log.info("t1已经被interrupt......");
                log.info("重置t1中断状态...");
                Thread.interrupted();
                if (!Thread.currentThread().isInterrupted()) {
    
    
                    log.info("t1状态恢复...");
                }
            } else {
    
    
                log.info("t1还没被interrupt");
            }
        });
        t1.start();
        t1.interrupt();
    }
}

次のメソッドの実行中に割り込みによって中断されると、 InterruptedException がスローされます: Thread.sleepjoinwait

2.スレッドを中断する

フラグビットを判断して、スレッドを中断するリターン割り込みは
動作を終了するrunメソッドですので、そのままリターンしてください

  • isInterrupted をフラグとして使用する

    @Slf4j
    public class StopThread {
          
          
        public static void main(String[] args) {
          
          
            Thread t1 = new Thread(() -> {
          
          
               if (Thread.currentThread().isInterrupted()) {
          
          
                   return;
               }
               for (;;) {
          
          
                   log.info("t1 is running...");
               }
            });
            t1.start();
            t1.interrupt();
        }
    }
    
  • フラグをカスタマイズする

    @Slf4j
    public class StopThread {
          
          
        private volatile static boolean flag = false;
    
        public static void main(String[] args) {
          
          
            Thread t1 = new Thread(() -> {
          
          
               if (flag) {
          
          
                   return;
               }
               for (;;) {
          
          
                   log.info("t1 is running...");
               }
            });
            t1.start();
            t1.interrupt();
            flag = true;
        }
    }
    

7.公園

LockSupport.parkのスレッドをスリープ状態にする (WAITING 状態にする) メソッドは、呼び出し後に直接スリープ状態に入るのではなく、まずPermitに従って判断し、 Permit が存在する場合は直接戻ります。存在しない場合、スリープ状態になります (許可が利用できない限り、スレッド スケジューリングの目的で現在のスレッドを無効にします。許可が利用可能な場合は、許可が消費され、呼び出しはすぐに戻ります。そうでない場合、現在のスレッドはスレッド スケジューリングの目的で無効になります) )

ハイバネーションが中断される状況は 3 つあります。

  • 別のスレッドがLockSupport.unparkメソッドを呼び出す
  • 通話の中断
  • その他の理由

LockSupport.parkNanos LockSupport.parkと比較するとLockSupport.park は時間を指定してスリープ状態 (TIMED_WAITING) に入る必要があり、ウェイクアップする必要はなく、時間が経過すると自然にウェイクアップします。

@Slf4j
public class Park_demo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread(() -> LockSupport.park(), "t1");
        Thread t2 = new Thread(() -> LockSupport.parkNanos(1000*1000*3000L), "t2");
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(1);
        log.info("t1's state: {}, t2's state: {}", t1.getState(), t2.getState());
    }
}

知らせ:

  • LockSupport.parkNanos は例外をスローしないため、スリープ中に割り込みを使用して割り込みを行っても例外はスローされません

  • LockSupport.unpark を使用して、スリープ状態のスレッドを目覚めさせることができます

    @Slf4j
    public class Park_demo {
          
          
        public static void main(String[] args) throws InterruptedException {
          
          
            Thread t1 = new Thread(() -> {
          
          
                LockSupport.park();
                log.info("t1苏醒...");
            }, "t1");
            t1.start();
            TimeUnit.SECONDS.sleep(1);
            log.info("t1's state: {}, t1's status: {}", t1.getState(), t1.isInterrupted());
            LockSupport.unpark(t1);
            log.info("t1's state: {}, t1's status: {}", t1.getState(), t1.isInterrupted());
        }
    }
    

8.セットデーモン

このセクションの参考記事: https://www.cnblogs.com/lixuan1998/p/6937986.html

setDaemon(true) は、スレッドをデーモン スレッドとして設定します。

Java スレッドの分類:

  • ユーザースレッド
  • デーモン スレッドは、
    プログラムの実行中にバックグラウンドで一般的なサービスを提供するスレッドです。たとえば、ガベージ コレクション スレッドは非常に有能な保護者ですが、このスレッドはプログラムの不可欠な部分ではありません。したがって、デーモン以外のスレッドがすべて終了すると、プログラムは終了し、プロセス内のすべてのデーモン スレッドが強制終了されます。逆に、デーモン以外のスレッドがまだ実行されている限り、プログラムは終了しません。最も典型的なデーモン スレッド: ガベージ コレクション スレッド。
  • 2 つの違い: デーモン スレッドとユーザー スレッドの間に本質的な違いはありません。唯一の違いは仮想マシンの終了にあります。ユーザー スレッドがすべて終了し、デーモン スレッドのみが存在する場合、仮想マシンはも出る。ガーディアンがないため、デーモン スレッドは何もする必要がなく、プログラムを実行し続ける必要はありません。

デーモン スレッドを使用する場合の注意事項:

  • setDaemon(true) はstart の前に使用する必要があります。そうしないと、例外IllegalThreadStateExceptionがスローされます。
  • デーモンスレッド内で生成された新しいスレッドもデーモンスレッドです
  • デーモン スレッドは、操作の途中であってもいつでも中断される可能性があるため、ファイルやデータベースなどの固有のリソースには決してアクセスしてはなりません。
@Slf4j
public class Daemon_md {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> log.info("t1.isDaemon: {}", Thread.currentThread().isDaemon()), "t1");
        Thread t2 = new Thread(()->{
    
    
            t1.start();
            Thread t3 = new Thread("t3");
            t3.start();
            log.info("t2.isDaemon: {}", Thread.currentThread().isDaemon());
            log.info("t3.isDaemon: {}", t3.isDaemon());
        }, "t2");
        t2.setDaemon(true);
        t2.start();
        log.info("main.isDaemon: {}", Thread.currentThread().isDaemon());
    }
}

おすすめ

転載: blog.csdn.net/m0_54355172/article/details/128712835