Java学習ルート(20) - マルチスレッド

1. スレッド

1. 概念:プログラム内の実行パス

2. 分類

  • シングルスレッド:プログラム内の実行パスは 1 つだけです
  • マルチスレッド:プログラム内の複数の実行パス

2 番目に、マルチスレッドの作成

1. Thread の概念: Java は、java.lang.Thread クラスを通じてスレッドを表し、オブジェクト指向の原則に従って、Thread クラスはマルチスレッド実装メソッドを提供します。

2. 方法 1: Thread クラスを継承する

(1) 導入プロセス

  • Thread クラスを継承し、run() メソッドをオーバーライドするスレッド サブクラスを定義します。
  • スレッド実装クラスオブジェクトを作成する
  • スレッド オブジェクトの start() メソッドを呼び出してスレッドを開始します (run() メソッドは起動後も実行されます)。

(2) 使用例

public class ThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        MyThread thread = new MyThread("线程1运行中...");
        MyThread thread2 = new MyThread("线程2运行中...");
        thread.start();
        thread2.start();
    }
}

class MyThread extends Thread{
    
    

    private String message;

    public MyThread(){
    
    }

    public MyThread(String message){
    
    
        this.message = message;
    }
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(message);
        }
    }
}

/*打印输出*/
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...

(3) メリットとデメリット

  • 長所:コーディングが簡単
  • 欠点: thread クラスは Thread クラスを継承し、他のクラスを継承できないため、拡張には役立ちません。

(4) 関連する問題

Q: メインスレッドのタスクを子スレッドの前に配置できますか?
回答:マルチスレッドを実装したい場合は、メインスレッドのタスクをサブスレッドの開始後に配置する必要があります。そうしないと、CPU は最初にメインスレッドのタスクを実行し、次にサブスレッドを実行します。

Q: run() を直接呼び出さず、start() を呼び出すのはなぜですか?
回答: run() が呼び出されると、通常のメソッドとして実行されます。実際にはまだシングルスレッドですが、start() がオンになった後は、マルチスレッド実行とみなされます。

3. 方法 2: Runnable インターフェイスを実装する

(1) 実施方法

  • Runnable インターフェイスを実装し、run() メソッドをオーバーライドするスレッド タスク クラスを定義します。
  • スレッドタスククラスオブジェクトの作成
  • スレッドタスククラスオブジェクトをスレッドに渡して処理します
  • start() を呼び出してスレッドを開始します

(2) 使用例

public class ThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        RunnableThread run = new RunnableThread("线程1运行中...");
        RunnableThread run1 = new RunnableThread("线程2运行中...");
        Thread t = new Thread(run);
        Thread t1 = new Thread(run1);
        t.start();
        t1.start();
    }
}

class RunnableThread implements Runnable{
    
    

    private String message;

    public RunnableThread(){
    
    }

    public RunnableThread(String message){
    
    
        this.message = message;
    }

    @Override
    public void run() {
    
    
        System.out.println(message);
    }
}

/*打印输出*/
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...

(3) メリットとデメリット

  • 利点:スレッド タスク クラスはインターフェイスのみを実装し、他のクラスを継承して他のインターフェイスを実装でき、強力なスケーラビリティを備えています。
  • 短所:プログラミング用のオブジェクト パッケージ化の追加レイヤーがあり、スレッドに実行結果がある場合、それを直接返すことができません。

(4) 匿名内部クラスの実装方式

public class ThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                System.out.println("子线程1运行中");
            }
        });

        Thread t2 = new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                System.out.println("子线程2运行中");
            }
        });

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

4. 方法 3: Callable インターフェイスを実装する

(1) 使用シナリオ:スレッドが結果を直接返す必要がある場合。

(2) 実施方法

  • タスクオブジェクトを取得する
    • Callable インターフェースを実装するクラスを定義し、call メソッドを書き換えてタスクをカプセル化します。
    • FutureTask を使用して Callable オブジェクトをスレッド タスク オブジェクトにカプセル化する
  • スレッド タスク オブジェクトを処理のために Thread に渡します。
  • start() を呼び出してタスクを実行する
  • 電話することで

(3) 実装例

public class CallableDemo {
    
    
    public static void main(String[] args) {
    
    
        /*获取线程任务类对象*/
        Callable<String> myCallable = new MyCallable(10);
        Callable<String> myCallable1 = new MyCallable(20);

        /*FutureTask 封装现成任务类对象*/
        FutureTask<String> f1 = new FutureTask<>(myCallable);
        FutureTask<String> f2 = new FutureTask<>(myCallable1);

        /*交给Thread执行*/
        Thread t = new Thread(f1);
        Thread t1 = new Thread(f2);

        t.start();
        t1.start();

        /*FutureTask 调用get方法获取返回值*/
        try {
    
    
            System.out.println(f1.get());
            System.out.println(f2.get());
        } catch (InterruptedException | ExecutionException e) {
    
    
            throw new RuntimeException(e);
        }
    }
}

class MyCallable implements Callable<String>{
    
    

    private int num;

    public MyCallable() {
    
    
    }

    public MyCallable(int num) {
    
    
        this.num = num;
    }

    @Override
    public String call() throws Exception {
    
    
        int sum = 0;
        for (int i = 1; i < num; i++) {
    
    
            sum += i;
        }
        return "子线程执行结果为 " + sum;
    }
}

/*打印输出*/
子线程执行结果为 45
子线程执行结果为 190

三、スレッド共通API

1. 質問: 複数のスレッドが同時に実行されている場合、スレッドをどのように区別すればよいですか?

方法 説明する
文字列 getName() 現在のスレッドの名前を取得します
文字列セット名() 現在のスレッドの名前を変更します
スレッド currentThread() 現在のスレッドオブジェクトを取得します

2. 実装例

  public class MultiThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        /*创建线程*/
        Thread t1 = new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                System.out.println(Thread.currentThread().getName()+"输出:"+i);
            }
        });
        Thread t2 = new Thread(()->{
    
    
            for (int i = 0; i < 10; i++) {
    
    
                System.out.println(Thread.currentThread().getName()+"输出:"+i);
            }
        });
        /*重命名线程*/
        t1.setName("子线程1");
        t2.setName("子线程2");
        /*启动线程*/
        t1.start();
        t2.start();
    }
}

/*打印输出*/
子线程1输出:0
子线程2输出:0
子线程1输出:1
子线程2输出:1
子线程1输出:2
子线程2输出:2
子线程1输出:3
子线程2输出:3
子线程1输出:4
子线程2输出:4
子线程1输出:5
子线程2输出:5
子线程1输出:6
子线程2输出:6
子线程1输出:7
子线程2输出:7
子线程1输出:8
子线程2输出:8
子线程1输出:9
子线程2输出:9

3. スレッド名を変更するスキーム - 親クラスのコンストラクター

public class MultiThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        OThread o1 = new OThread("子线程1");
        OThread o2 = new OThread("子线程2");
        o1.start();
        o2.start();
    }
}

class OThread extends Thread{
    
    
    public OThread(){
    
    }

    public OThread(String name){
    
    
        super(name);
    }

    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+"执行输出:" + i);
        }
    }
}

/*打印输出*/
子线程1执行输出:0
子线程2执行输出:0
子线程1执行输出:1
子线程2执行输出:1
子线程1执行输出:2
子线程2执行输出:2
子线程1执行输出:3
子线程2执行输出:3
子线程1执行输出:4
子线程2执行输出:4
子线程1执行输出:5
子线程2执行输出:5
子线程1执行输出:6
子线程2执行输出:6
子线程1执行输出:7
子线程2执行输出:7
子线程1执行输出:8
子线程2执行输出:8
子线程1执行输出:9
子线程2执行输出:9

4. Threadクラスのスレッドスリープ

方法 説明する
静的ボイドスリープ(長時間) 現在のスレッドを指定された時間スリープさせてから、実行を続行します。単位はミリ秒です。

5. スレッドコンストラクター

コンストラクタ 説明する
スレッド(文字列名) 現在のスレッドに名前を割り当てる
スレッド(実行可能なターゲット) Runnable オブジェクトをスレッド オブジェクトに渡します
スレッド(実行可能なターゲット、文字列名) Runnable オブジェクトをスレッド オブジェクトに与え、現在のスレッドの名前を指定します。

4. スレッドの安全性

1. 関連する問題

質問 1: スレッドの安全性の問題とは何ですか?

回答: 複数のスレッドが共有データを同時に操作すると、ビジネス セキュリティの問題が発生する可能性があります。これは、スレッド セキュリティの問題と呼ばれます。

質問 2: スレッド セーフティの問題の原因は何ですか?

回答: マルチスレッドの同時実行では、複数のスレッドが同時に共有データにアクセスして変更するため、共有データが複数回変更されることになります。

public class ThreadSafeDemo {
    
    

    /*创建一个共享账号,并存入10W元*/
    public static Account account = new Account("ZGYH-1311",100000);

    public static void main(String[] args) {
    
    
        CardThread user1 = new CardThread(account,"小明");
        CardThread user2 = new CardThread(account,"小红");

        user1.start();
        user2.start();
    }
}

class Account{
    
    
    private String CARD;
    private double money;

    public Account() {
    
    
    }

    public Account(String CARD, double money) {
    
    
        this.CARD = CARD;
        this.money = money;
    }

    public String getCARD() {
    
    
        return CARD;
    }

    public void setCARD(String CARD) {
    
    
        this.CARD = CARD;
    }

    public double getMoney() {
    
    
        return money;
    }

    public void setMoney(double money) {
    
    
        this.money = money;
    }

    public void drawnMoney(double money){
    
    
        if (money <= this.money){
    
    
            this.money -= money;
            System.out.println(Thread.currentThread().getName()+"成功取出"+money+"元");
        }else {
    
    
            System.out.println("余额不足,无法取出");
        }
    }
}

class CardThread extends Thread{
    
    
    private Account account;

    public CardThread(Account account,String name) {
    
    
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
    
    
        account.drawnMoney(100000);
    }
}

/*打印输出*/
余额不足,无法取出
小明成功取出100000.0

2. 解決策: スレッドの同期


5、スレッド同期

1. スレッド同期の概念:複数のスレッドが同じ操作を同期的に実行します。

2. スレッド同期の基本的な考え方

  • ロック:共有リソースをロックします。毎回 1 つのスレッドのみがアクセスを許可され、他のスレッドは現在のスレッドがアクセスした後にのみアクセスできます。

3. 同期の実装方法
(1) 方法 1: コードブロックを同期する

  • 機能:スレッド セーフティの問題がある可能性のあるコア コードをロックします。
  • 原則:一度に 1 つのスレッドのみが参加でき、実行後に自動的にロックが解除され、他のスレッドが参加して実行できます。
  • フォーマット
synchronized(同步锁对象){
    
    
	操作共享资源的代码(核心代码)
}
  • ロック オブジェクトの要件:

理論的には、ロック オブジェクトが同時に実行中のスレッドと同じオブジェクトである限り、関係のない他のスレッドにも影響を与えるため、ロック オブジェクトの仕様が存在します。

仕様
a. 共有リソースをロック オブジェクトとして使用することを推奨します
b. インスタンス メソッドの場合、this キーワードを使用することを推奨します
c. 静的メソッドの場合、ロック オブジェクトとして .class オブジェクトを使用することを推奨します

/*以上面的例子,改变为是将核心代码加同步锁*/
    public void drawnMoney(double money){
    
    
        synchronized (this){
    
    
            if (money <= this.money){
    
    
                this.money -= money;
                System.out.println(Thread.currentThread().getName()+"成功取出"+money+"元");
            }else {
    
    
                System.out.println("余额不足,无法取出");
            }
        }
    }

/*打印输出*/
小明成功取出100000.0元
余额不足,无法取出

/*静态方法*/
    public static void print(String message){
    
    
        synchronized (Account.class){
    
    
            System.out.println(message);
        }
    }

(2) 同期方法
使用方法は同期コードブロックと同様です。

  • 機能: synchronized キーワードを使用して、スレッド セーフティの問題があるメソッドをロックします。
  • フォーマット
修饰符 synchronized 返回值类型 方法名称(形参列表){
    
    
	操作代码....
}

	public synchronized void drawnMoney(double money){
    
    
           if (money <= this.money){
    
    
               this.money -= money;
               System.out.println(Thread.currentThread().getName()+"成功取出"+money+"元");
           }else {
    
    
               System.out.println("余额不足,无法取出");
           }
   }
  • 根底にある原理
    • 実際、同期メソッドの最下層にも暗黙的なロック オブジェクトがあります。ロック メソッドがインスタンス メソッドの場合、これがデフォルトでロック オブジェクトとして使用されます。メソッドが静的メソッドの場合、.class がロック オブジェクトとして使用されます。デフォルトではオブジェクトをロックします。

関連する質問

質問 1: 同期コード ブロックと同期メソッドの比較

回答: 同期コード ブロックのロック範囲は小さく、同期メソッドのロック範囲は大きいです。

(3)ロックロック

  • 概念: ロックのロックと解放の方法をより明確に表現するために、JDK5 は新しいロック オブジェクトLock Later を提供します。Lock 実装は、同期されたメソッドやステートメントを使用するよりも広範なロック操作を提供します。

  • オブジェクトタイプ:インターフェース

  • 作成方法:

コンストラクタ 説明する
リエントラントロック() Lockロック実装クラスの取得
  • 共通API
方法 説明する
ボイドロック() ロックを取得する
無効なロック解除() ロックを解除する

手順

  • オブジェクトクラスで定数Lockを宣言します。
  • コアコードでlock()、unlock()を使用する
class Account {
    
    
    private String CARD;
    private double money;

    private final Lock lock = new ReentrantLock();

    public Account() {
    
    
    }

    public Account(String CARD, double money) {
    
    
        this.CARD = CARD;
        this.money = money;
    }

    public void drawnMoney(double money) {
    
    
        lock.lock();
        /*为避免因为异常导致死锁,所以要捕捉异常并解锁*/
        try {
    
    
            if (money <= this.money) {
    
    
                this.money -= money;
                System.out.println(Thread.currentThread().getName() + "成功取出" + money + "元");
            } else {
    
    
                System.out.println("余额不足,无法取出");
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            lock.unlock();
        }
    }

    public static void print(String message) {
    
    
        synchronized (Account.class) {
    
    
            System.out.println(message);
        }
    }
}

6. スレッド通信

1. 概念:スレッドは通常、共有データ通信を通じて相互にデータを送信します。

2. 一般的な通信モデル

  • プロデューサおよびコンシューマ モデル:プロデューサ スレッドはデータの生成を担当し、コンシューマ スレッドはデータの消費を担当します
    実装方法:プロデューサ スレッドがデータを生成した後、コンシューマをウェイクアップし、コンシューマがデータを消費した後、コンシューマ自体を待機します、プロデューサーを起こしてから、自分自身を待ちます。
    起きて待ってて
方法 説明する
void wait() 現在のスレッドは起動を待っています
無効通知() 現在のスレッドは起動を待っています
void notificationAll() 現在のスレッドは起動を待っています

実装例

public class ThreadCommunication {
    
    

    public static Card account = new Card("ZGYH-1311",0);

    public static void main(String[] args) {
    
    
        /*创建爹对象,生产者*/
        Saver saver1 = new Saver(account,"亲爹");
        Saver saver2 = new Saver(account,"干爹");
        Saver saver3 = new Saver(account,"岳父");

        /*创建子辈对象,消费者*/
        Customer customer1 = new Customer(account,"小明");
        Customer customer2 = new Customer(account,"小红");

        saver1.start();
        saver2.start();
        saver3.start();
        customer1.start();
        customer2.start();

    }
}

class Card{
    
    
    private String card;
    private double money;

    private final Lock lock = new ReentrantLock();

    public Card(){
    
    }

    public Card(String card,double money){
    
    
        this.card = card;
        this.money = money;
    }

    public String getCard() {
    
    
        return card;
    }

    public double getMoney() {
    
    
        return money;
    }

    public void setMoney(double money) {
    
    
        this.money = money;
    }

    public synchronized void save(double money){
    
    
        try {
    
    
            if (this.money == 0 ){
    
    
                this.money += money;
                System.out.println(Thread.currentThread().getName()+"已向"+this.getCard()+"存入金额"+money+"元,剩余金额:"+this.getMoney());
            }
            this.notifyAll();
            this.wait();
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

    public synchronized void get(double money){
    
    
        this.notify();
        try {
    
    
            if (this.money != 0){
    
    
                this.money -= money;
                System.out.println(Thread.currentThread().getName()+"取出金额"+money+",剩余金额:"+this.getMoney());
            }
            this.notifyAll();
            this.wait();
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

class Saver extends Thread{
    
    
    private Card account;

    public Saver(){
    
    }

    public Saver(Card account,String name) {
    
    
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
    
    
        while (true){
    
    
            account.save(100000);
            try {
    
    
                sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
}

class Customer extends Thread{
    
    
    private Card account;

    public Customer(){
    
    }

    public Customer(Card account,String name) {
    
    
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
    
    
        while (true){
    
    
            account.get(100000);
            try {
    
    
                sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
}

/*打印输出*/
亲爹已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小明取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小红取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小明取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小红取出金额100000.0,剩余金额:0.0
干爹已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小明取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小红取出金额100000.0,剩余金额:0.0
亲爹已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小明取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小红取出金额100000.0,剩余金额:0.0


7、スレッドプール

1. スレッド プールの概念:スレッド プールは、スレッドを再利用できる技術です。
2. スレッド プールを使用する理由:ユーザーがリクエストを開始するたびに、新しいスレッドがバックグラウンドで作成され、次の新しいタスク リクエストが行われると、新しい郡が作成され、新しいスレッドが作成されます。システムリソースに非常に高価です。
3. スレッド プールの動作原理:
(1) スレッド プールは、ワーカー スレッド WorkThread とタスク キュー WorkQueue の 2 つの部分で構成されます。
(2) 新しいタスク リクエストがタスク キューに追加され、ワー​​カー スレッドが構成されます。複数のスレッドの各スレッド タスクが完了すると、タスクキューからリクエストが呼び出されて処理されます。
4. スレッドプールAPI
(1) インターフェース: ExecutorService
(2) 作成方法:

  • 方法 1: ExecutorService の実装クラス ThreadPoolExecutor を使用してスレッド プール オブジェクトを作成する
public ThreadPoolExecutor(int corePoolSize, 	//线程池的线程数量(核心线程)
                              int maximumPoolSize,	//线程池支持的最大线程数
                              long keepAliveTime,	//临时线程的最大存货时间
                              TimeUnit unit,	//存活时间的单位(时、分、秒、天)
                              BlockingQueue<Runnable> workQueue,	//指定任务队列
                              ThreadFactory threadFactory,	//制定线程工厂
                              RejectedExecutionHandler handler)	//指定线程忙,任务满时,新任务请求的处理

新しいタスク拒否ポリシー

ストラテジー 説明する
ThreadPoolExecutor.AbortPolicy RejectedExecutionException を破棄してスローします (デフォルト)
ThreadPoolExecutor.DiscardPolicy タスクを直接ドロップする
ThreadPoolExecutor.DiscardOldestPolicy キュー内で最も長く待機しているタスクを破棄し、Ren Han が現在のタスクをキューに追加します
ThreadPoolExecutor.CallerRunsPolicy メインスレッドはタスクの run() メソッドを呼び出す責任があるため、スレッド プールをバイパスして直接実行できます。

質問 1: 一時スレッドはいつ作成されますか?

回答: 新しいタスクが送信されると、コア スレッドがビジーであり、タスク キューがいっぱいで、一時スレッドを作成できることが判明した場合、一時スレッドが作成されます。

質問 2: タスクを拒否し始めるのはいつですか?

回答: コア スレッドと一時スレッドの両方がビジー状態で、タスク キューがいっぱいで、新しいタスクが来るとタスクは拒否されます。

(3) ExecutorServiceの共通メソッド

方法 説明する
voidexecute(実行可能なコマンド) タスクを実行します。戻り値はありません。通常、実行可能なタスクを実行するために使用されます。
voidexecute(Callable<T>タスク) タスクを実行し、スレッド結果を取得するために将来のタスク オブジェクトを返します。通常、呼び出し可能なタスクを実行するために使用されます。
今後の送信(実行可能なコマンド) 実行可能なタスクがタスクキューに追加されます
将来の送信(Callable<T> タスク) 呼び出し可能なタスクがタスクキューに追加されます
void シャットダウン() タスクの実行後にスレッド プールを閉じる
List<Runnable> shutdownNow()() close と同様に、実行中のタスクを停止し、キュー内の未実行のタスクを返します。

(4) スレッドプールの実行可能タスクの処理

  • スレッドプールオブジェクトを作成する
/*创建含有3个核心线程,5个最大线程数,8秒线程存活时间,6个容量的任务队列,默认线程工厂,丢弃任务并抛出异常的线程池*/
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

(5) スレッドプールの処理呼び出し可能タスク

public class ThreadPoolDemo {
    
    
    public static void main(String[] args) {
    
    
        /*创建含有3个核心线程,5个最大线程数,8秒线程存活时间,6个容量的任务队列,默认线程工厂,拒绝任务的线程池*/
        ExecutorService executorService = new ThreadPoolExecutor(3,5,5, TimeUnit.SECONDS,new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        Runnable runnable = new newRunnable();

        /*模拟10个任务进入线程池*/
        for (int i = 0; i < 10; i++) {
    
    
            executorService.execute(runnable);
        }

        executorService.shutdown();
    }
}

class newRunnable implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i = 0; i < 5; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "输出:" + i + "时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(System.currentTimeMillis()));
        }
    }
}

/*打印输出*/
pool-1-thread-3输出:0时间:2023-06-01 12:00:59.783
pool-1-thread-1输出:0时间:2023-06-01 12:00:59.783
pool-1-thread-2输出:0时间:2023-06-01 12:00:59.783
pool-1-thread-4输出:0时间:2023-06-01 12:00:59.783
pool-1-thread-3输出:1时间:2023-06-01 12:00:59.818
pool-1-thread-2输出:1时间:2023-06-01 12:00:59.818
pool-1-thread-1输出:1时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:1时间:2023-06-01 12:00:59.818
pool-1-thread-3输出:2时间:2023-06-01 12:00:59.818
pool-1-thread-2输出:2时间:2023-06-01 12:00:59.818
pool-1-thread-1输出:2时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:2时间:2023-06-01 12:00:59.818
pool-1-thread-3输出:3时间:2023-06-01 12:00:59.818
pool-1-thread-2输出:3时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:3时间:2023-06-01 12:00:59.818
pool-1-thread-1输出:3时间:2023-06-01 12:00:59.818
pool-1-thread-3输出:4时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:4时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:4时间:2023-06-01 12:00:59.819
pool-1-thread-2输出:4时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:0时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:0时间:2023-06-01 12:00:59.819
pool-1-thread-4输出:1时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:1时间:2023-06-01 12:00:59.819
pool-1-thread-2输出:0时间:2023-06-01 12:00:59.819
pool-1-thread-4输出:2时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:2时间:2023-06-01 12:00:59.819
pool-1-thread-3输出:0时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:3时间:2023-06-01 12:00:59.819
pool-1-thread-2输出:1时间:2023-06-01 12:00:59.819
pool-1-thread-4输出:3时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:4时间:2023-06-01 12:00:59.819
pool-1-thread-3输出:1时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:0时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:4时间:2023-06-01 12:00:59.820
pool-1-thread-2输出:2时间:2023-06-01 12:00:59.819
pool-1-thread-3输出:2时间:2023-06-01 12:00:59.820
pool-1-thread-1输出:1时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:0时间:2023-06-01 12:00:59.820
pool-1-thread-2输出:3时间:2023-06-01 12:00:59.820
pool-1-thread-3输出:3时间:2023-06-01 12:00:59.820
pool-1-thread-1输出:2时间:2023-06-01 12:00:59.820
pool-1-thread-2输出:4时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:1时间:2023-06-01 12:00:59.820
pool-1-thread-3输出:4时间:2023-06-01 12:00:59.820
pool-1-thread-1输出:3时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:2时间:2023-06-01 12:00:59.820
pool-1-thread-1输出:4时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:3时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:4时间:2023-06-01 12:00:59.821
  • 方法 2: Executor (スレッド プール ツール クラス) を使用してメソッドを呼び出し、異なる特性を持つスレッド プール オブジェクトを返す

共通API

方法 説明する
静的 ExecutorService newCachedThreadPool() タスクの増加に応じてスレッドの数も増加します。スレッド タスクが実行され、一定期間アイドル状態になると、スレッドはリサイクルされます。
static ExecutorService newFixedThreadPool(int nThreads) 创建固定数量的线程池,如果某个线程执行发生异常而结束,则线程池补充新线程替代
static ExecutorService newSingleThreadExecutor() 相当于 newFixedThreadPool(1)
static ScheduledExecutorService newScheduledThreadPool(int corePoolSite) 创建线程池,可以实现给定延迟后运行任务或定期完成任务

Excutors底层原理: 基于线程池实现类ThreadPoolExecutor创建线程池对象。

使用示例

**任务类 newRunnable **

class newRunnable implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 5; i++) {
    
    
            System.out.println(Thread.currentThread().getName() + "输出:" + i + "时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(System.currentTimeMillis()));
        }
    }
}

newCacheThreadPool()

public class ExecutorsDemo {
    
    
    public static void main(String[] args) {
    
    
        Runnable runnable = new newRunnable();
        System.out.println("——————————newCacheThreadPool——————————");
        /*线程数随着任务数量的变化而变化*/
        ExecutorService e = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
    
    
            e.submit(runnable);
        }
        e.shutdown();
    }
}

/*打印输出*/
——————————newCacheThreadPool——————————
pool-1-thread-2输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-5输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-4输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-2输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-3输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-1输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-2输出:2时间:2023-06-01 13:55:43.638
pool-1-thread-4输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-3输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-5输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-2输出:3时间:2023-06-01 13:55:43.638
pool-1-thread-1输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-3输出:2时间:2023-06-01 13:55:43.639
pool-1-thread-4输出:2时间:2023-06-01 13:55:43.639
pool-1-thread-2输出:4时间:2023-06-01 13:55:43.639
pool-1-thread-5输出:2时间:2023-06-01 13:55:43.639
pool-1-thread-1输出:2时间:2023-06-01 13:55:43.639
pool-1-thread-4输出:3时间:2023-06-01 13:55:43.639
pool-1-thread-3输出:3时间:2023-06-01 13:55:43.639
pool-1-thread-5输出:3时间:2023-06-01 13:55:43.639
pool-1-thread-1输出:3时间:2023-06-01 13:55:43.639
pool-1-thread-4输出:4时间:2023-06-01 13:55:43.639
pool-1-thread-3输出:4时间:2023-06-01 13:55:43.639
pool-1-thread-5输出:4时间:2023-06-01 13:55:43.639
pool-1-thread-1输出:4时间:2023-06-01 13:55:43.639

newFixedThreadPool

public class ExecutorsDemo {
    
    
    System.out.println("——————————newFixedThreadPool——————————");
	ExecutorService e2 = Executors.newFixedThreadPool(2);
	for (int i = 0; i < 5; i++) {
    
    
           e2.submit(runnable);
        }

        e2.shutdown();
}

/*打印输出*/
——————————newFixedThreadPool——————————
pool-1-thread-1输出:0时间:2023-06-01 14:17:21.888
pool-1-thread-2输出:0时间:2023-06-01 14:17:21.888
pool-1-thread-1输出:1时间:2023-06-01 14:17:21.919
pool-1-thread-2输出:1时间:2023-06-01 14:17:21.919
pool-1-thread-1输出:2时间:2023-06-01 14:17:21.920
pool-1-thread-2输出:2时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:3时间:2023-06-01 14:17:21.920
pool-1-thread-2输出:3时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:4时间:2023-06-01 14:17:21.920
pool-1-thread-2输出:4时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:0时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:1时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:2时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:3时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:0时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:4时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:1时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:0时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:2时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:1时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:3时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:2时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:4时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:3时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:4时间:2023-06-01 14:17:21.921

newSingleThreadExecutor

public class ExecutorsDemo {
    
    
        System.out.println("——————————newSingleThreadExecutor——————————");
        ExecutorService e3 = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
    
    
            e3.submit(runnable);
        }

        e3.shutdown();
}

/*打印输出*/
——————————newSingleThreadExecutor——————————
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.093
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.120
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.123

newScheduledThreadPool

public class ExecutorsDemo {
    
    
        System.out.println("——————————newScheduledThreadPool——————————");
        ExecutorService e4 = Executors.newScheduledThreadPool(20);
        for (int i = 0; i < 5; i++) {
    
    
            e4.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Thread.currentThread().getName()+"执行了");
                }
            });
        }

        e4.shutdown();
}

/*打印输出*/
——————————newScheduledThreadPool——————————
pool-1-thread-1执行了
pool-1-thread-3执行了
pool-1-thread-2执行了
pool-1-thread-4执行了
pool-1-thread-5执行了

使用Executors工具类可能产生的问题

  • 固定线程池与单例线程池产生的问题: 允许请求任务队列长度为Integer.MAX_VALUE,可能出现OOM错误(内存溢出)
  • 缓冲线程池与计划线程池产生的问题: 创建的线程数量为Integer.MAX_VALUE,线程数量随任务数量增加,可能会出现OOM错误。

八、定时器

1、概念: 是一种控制任务延时调用或周期调用的技术。

2、作用: 定时执行任务

3、实现方式
(1)Timer

构造器 说明
Timer() 创建计时器对象
方法 说明
void schedule(TimerTask task,long delay,long period) 创建带有任务,延时时间,间隔周期参数的执行方法

使用示例

public class TimerDemo {
    
    
    public static void main(String[] args) {
    
    
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"执行了,执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(System.currentTimeMillis()));
            }
        },1000,2000); //延时1s开始,以2s为一个执行周期
    }
}

/*打印输出*/
Timer-0执行了,执行时间:2023-06-01 15:49:56.834
Timer-0执行了,执行时间:2023-06-01 15:49:58.812
Timer-0执行了,执行时间:2023-06-01 15:50:00.818

存在的问题

  • Timer是单线程,当有多个任务执行时,存在延时和设置定时器的时间有差异。
  • 可能因为某个任务异常而终止,影响后续执行。

(2)ScheduleExecutorService

  • 优点: 基于线程池,某个任务执行情况不会影响到其它线程。
方法 说明
static ScheduledExecutorService newScheduledTreadPool(int corePoolSize) 得到线程池对象
ScheduledFuture<?> scheduleAtFixedRate(Rinnable command,long delay,long period, TimeUnit unit) 创建周期调度方法

使用示例


public class ScheduleDemo {
    
    
    public static void main(String[] args) {
    
    
        ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
        service.scheduleAtFixedRate(() -> {
    
    
            System.out.println(Thread.currentThread().getName()+"执行了,时间:"+new Date());
        },0,1, TimeUnit.SECONDS);
    }
}

九、补充知识
1、并发: 因CPU同时处理的线程有限,CPU会轮询每一个线程服务,由于CPU切换速度极快,因此我们会认为这些线程都是同时执行的,这就叫并发。
2、并行: CPU可多线程执行的能力叫并发。
3、生命周期: 是线程从创建到销毁的过程。
(1)六种状态

  • NEW——新建状态
  • RUNNABLE——运行状态
  • BLOCKED——锁阻塞状态
  • WATTING——等待状态
  • TIMED_WATING——计时等待状态
  • TERMINATED——终止死亡状态
    在这里插入图片描述

おすすめ

転載: blog.csdn.net/Zain_horse/article/details/130969982