Java マルチスレッド用のスレッド同期メカニズム (ロック、スレッド プールなど)

1.コンセプト

1. 同時実行性

同じオブジェクトが複数のスレッドで同時に操作される

2、起因

マルチスレッドの問題に対処する場合、複数のスレッドが同じオブジェクトにアクセスし、一部のスレッドがこのオブジェクトを変更したい場合は、スレッドの同期が必要です。スレッドの同期は、実際には待機メカニズムです。同時にこのオブジェクトにアクセスする必要がある複数のスレッドが入ります。このオブジェクト 待機プールはキューを形成し、前のスレッドが使い果たされるのを待ち、その後、次のスレッドがそれを使用します。ただし、同じプロセスの複数のスレッドが同じ記憶領域を共有するため、利便性が得られる一方でアクセスの競合が発生するため、メソッド内でアクセスされた際のデータの正当性を確保するために、ロック機構を追加します。

3. デメリット

1 つのスレッドがロックを保持すると、ロックを必要とする他のすべてのスレッドがハングします。
(1) マルチスレッドの競合下では、ロックの追加と解放によりコンテキストの切り替えとスケジューリングの遅延が増加し、パフォーマンスの問題が発生します。
(2) 優先度の高いスレッドが優先度の低いスレッドがロックを解放するのを待つと、優先度の逆転が発生し、パフォーマンス上の問題が発生します。

2、3 つの重大な危険なケース

1. 例1(チケット購入シーンのシミュレーション)

複数のスレッドが並行している場合、キューが適切に設計されていない場合、結果は安全ではなく、-1 が表示されます。

package com.example.multithreading.demo12_syn;

// 线程不安全,有负数
public class UnsafeBuyTicket {
    
    
    public static void main(String[] args) {
    
    
        BuyTicket station = new BuyTicket();

        new Thread(station, "自己").start();
        new Thread(station, "其他人").start();
        new Thread(station, "黄牛").start();
    }

}

class BuyTicket implements Runnable{
    
    

    // 票
    private int ticketNums = 10;
    // 外部停止方式
    boolean flag = true;

    @Override
    public void run() {
    
    
        // 买票
        while(flag) {
    
    
            try {
    
    
                buy();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException {
    
    
        // 判断是否有票
        if (ticketNums <= 0){
    
    
            flag = false;
            return;
        }
        // 模拟延时
        Thread.sleep(100);

        // 买票
        System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
    }
}

結果
ここに画像の説明を挿入

2. 例2(出金シーンのシミュレーション)

package com.example.multithreading.demo12_syn;

import lombok.Data;

public class UnsafeBank {
    
    
    public static void main(String[] args) {
    
    
        // 账户
        Account account = new Account(100,"基金");

        Drawing boy = new Drawing(account,50,"自己");
        Drawing girlFriend = new Drawing(account,100,"女朋友");

        boy.start();
        girlFriend.start();
    }
}

// 账户
@Data
class Account {
    
    
    // 余额
    int money;
    // 卡名
    String name;

    public Account(int money, String name) {
    
    
        this.money = money;
        this.name = name;
    }
}

// 模拟取钱
@Data
class Drawing extends Thread{
    
    
    // 账户
    Account account;
    // 取钱数
    int drawingMoney;
    // 持有钱数
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
    
    
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
        this.nowMoney = nowMoney;
    }

    @Override
    public void run() {
    
    
        // 判断是否有钱
        if (account.money - drawingMoney < 0){
    
    
            System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
            return;
        }

        try{
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        // 卡内余额 = 余额 - 取的钱
        account.money = account.money - drawingMoney;
        // 你手里的钱
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + "余额为:" + account.money);
        System.out.println(this.getName() + "手里的钱:" + nowMoney);
    }
}


結果
ここに画像の説明を挿入

3. 例3(シミュレーション収集)

package com.example.multithreading.demo12_syn;

import java.util.ArrayList;
import java.util.List;

// 线程不安全的集合
public class UnsafeList {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
    
    
            new Thread(()->{
    
    
                list.add(Thread.currentThread().getName());
            }).start();
        }

        System.out.println(list.size());
    }
}

結果 (実際には 1000 になるはずです)
ここに画像の説明を挿入

3. 同期方式と同期ブロック

1. 同期方法

private キーワードを使用すると、メソッドによってのみデータ オブジェクトにアクセスできるようにすることができるため、メソッドのメカニズム (synchronized キーワード) を提案するだけで済みます。2つの使い方(同期メソッドと同期ブロック)

public synchronized void method(int args){
    
    }

同期メソッドは、「オブジェクト」へのアクセスを制御します。各オブジェクトはロックに対応します。各同期メソッドは、実行する前にメソッドを呼び出すオブジェクトのロックを取得する必要があります。そうでない場合、スレッドはブロックされます。メソッドが実行されると、 、メソッドが戻った後にのみロックが解放され、ブロックされたスレッドがロックを取得して実行を継続できるようになるまで、ロックは排他的に占有されます。(欠点: 大規模なメソッドが同期済みとして宣言されると、効率に影響します)
ここに画像の説明を挿入

2. 同期ブロック

(1) 形式

synchronized(obj){
    
    }

(2) Obj は同期モニターと呼ばれます。Obj
は任意のオブジェクトでかまいませんが、共有リソースを同期モニターとして使用することを推奨します。
同期メソッドでは同期モニターを指定する必要はありません。メソッドは this であり、オブジェクト自体です。
(3) 同期モニタの実行プロセス
最初のスレッドは同期モニタにアクセスしてロックし、同期モニタ内のコードを実行します。
2 番目のスレッドがアクセスすると、同期モニターがロックされており、アクセスできないことがわかります。
最初のスレッドがアクセスした後、同期モニターのロックが解除されます。
2 番目のスレッドがアクセスし、同期モニターにロックがないことがわかり、ロックしてアクセスします。
ここに画像の説明を挿入

4. JUC セキュリティ タイプのコレクション

1. スレッドセーフなコレクション (遅延実装と組み合わせた)

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

2. サンプル

package com.example.multithreading.demo12_syn;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

// 线程不安全的集合
public class UnsafeList {
    
    
    public static void main(String[] args) {
    
    
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 1000; i++) {
    
    
            new Thread(()->{
    
    
                list.add(Thread.currentThread().getName());
            }).start();
        }

        try{
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        System.out.println(list.size());
    }
}

結果
ここに画像の説明を挿入

5、デッドロック

1.コンセプト

マルチスレッドはそれぞれ共有リソースを占有し、他のスレッドが占有しているリソース上で互いに実行されるのを待機します。その結果、2 つ以上のスレッドが互いにリソースを解放して実行を停止するのを待機する状況が発生します。同期ブロックが「2つ以上のオブジェクトのロック」を同時に保持すると、「デッドロック」の問題が発生することがあります。

2. デッドロックサンプル

package com.example.multithreading.demo13_DeadLock;

// 死锁:多个线程互相占有需要的资源,然后形成僵持
public class DeadLock {
    
    

    public static void main(String[] args) {
    
    
        Makeup g1 = new Makeup(0,"张三");
        Makeup g2 = new Makeup(1,"李四");

        g1.start();
        g2.start();
    }
}

class Lipstick {
    
    

}

class Mirror {
    
    

}

class Makeup extends Thread {
    
    
    // 需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    // 选择
    int choice;
    // 女孩
    String girlName;

    Makeup(int choice, String girlName) {
    
    
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
    
    
        try {
    
    
            makeup();
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        }
    }

    private void makeup() throws InterruptedException {
    
    
        if (choice == 0){
    
    
            synchronized (lipstick){
    
    
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);

                synchronized (mirror){
    
    
                    System.out.println(this.girlName + "获得镜子的锁");
                }
            }
        } else{
    
    
            synchronized (mirror){
    
    
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(2000);

                synchronized (lipstick){
    
    
                    System.out.println(this.girlName + "获得看口红的锁");
                }
            }
        }
    }
}

結果(ここで行き詰まります)
ここに画像の説明を挿入

3. 解決策

package com.example.multithreading.demo13_DeadLock;

// 死锁:多个线程互相占有需要的资源,然后形成僵持
public class DeadLock {
    
    

    public static void main(String[] args) {
    
    
        Makeup g1 = new Makeup(0,"张三");
        Makeup g2 = new Makeup(1,"李四");

        g1.start();
        g2.start();
    }
}

class Lipstick {
    
    

}

class Mirror {
    
    

}

class Makeup extends Thread {
    
    
    // 需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    // 选择
    int choice;
    // 女孩
    String girlName;

    Makeup(int choice, String girlName) {
    
    
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
    
    
        try {
    
    
            makeup();
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        }
    }

    private void makeup() throws InterruptedException {
    
    
        if (choice == 0){
    
    
            synchronized (lipstick){
    
    
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror){
    
    
                System.out.println(this.girlName + "获得镜子的锁");
            }
        } else{
    
    
            synchronized (mirror){
    
    
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(2000);
            }
            synchronized (lipstick){
    
    
                System.out.println(this.girlName + "获得看口红的锁");
            }
        }
    }
}


結果
ここに画像の説明を挿入

4. デッドロックの4つの必要条件

(1) 相互排他条件: リソースは一度に 1 つのプロセスによってのみ使用できます。
(2) 要求と保持の条件: リソースの要求によりプロセスがブロックされた場合、プロセスは取得したリソースを保持します。
(3) 非剥奪条件: プロセスによって取得されたリソースは、使い果たされる前に強制的に剥奪することはできません。
(4) 循環待ち条件: 複数のプロセス間で、先頭から末尾までの循環待ちリソース関係が形成されます。
デッドロックは、これらの条件の 1 つ以上を解消することで回避できます。

6、ロック(ロック)

1.コンセプト

JDK5.0 以降、Java はより強力なスレッド同期メカニズムを提供します。同期は同期ロック オブジェクトを明示的に定義することによって実現され、同期ロックは Lock オブジェクトを使用して動作します。
java.util.concurrent.locks.Lock インターフェースは、複数のスレッドによる共有リソースへのアクセスを制御するツールです。
ロックは共有リソースへの排他的アクセスを提供します。一度に 1 つのスレッドのみが Lock オブジェクトをロックでき、スレッドは共有リソースへのアクセスを開始する前に Lock オブジェクトを取得する必要があります。
ReentrantLock クラスは、同期と同じ同時実行性とメモリ セマンティクスを備えた Lock を実装します。スレッド セーフな制御を実装する場合は、明示的にロックとロックを解放できる ReentrantLock がより一般的に使用されます。

2. 同期とロックの比較

(1) ロックは明示的ロック (ロックを手動で開閉する) であり、同期は範囲外になると自動的に解放される暗黙的ロックです。
(2) Lock にはコード ブロック ロックのみがあり、synchronized にはコード ブロック ロックとメソッド ロックがあります。
(3) Lock ロックを使用すると、JVM はスレッドのスケジュールに費やす時間が短縮され、パフォーマンスが向上します。また、スケーラビリティが優れています (より多くのサブクラスを提供します)
優先
ロック > 同期コード ブロック (メソッド本体に入り、対応するリソースが割り当てられています) > 同期メソッド (メソッド本体の外)

3. サンプル

package com.example.multithreading.demo14_Lock;

import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    
    
    public static void main(String[] args) {
    
    
        TicketLock threadTest1 = new TicketLock();

        new Thread(threadTest1).start();
        new Thread(threadTest1).start();
        new Thread(threadTest1).start();
    }
}

class TicketLock implements Runnable{
    
    

    int ticketNums = 10;

    // 定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
    
    
        while (true) {
    
    
            try{
    
    
                // 加锁
                lock.lock();
                if(ticketNums > 0){
    
    
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                    System.out.println(ticketNums--);
                }else {
    
    
                    break;
                }
            }finally {
    
    
                // 解锁
                lock.unlock();
            }
        }
    }
}

結果
ここに画像の説明を挿入

七、スレッド連携

1.チューブ方式

プロデューサー: データの生成を担当するモジュール (メソッド、オブジェクト、スレッド、プロセスの場合があります)
コンシューマー: データの処理を担当するモジュール (メソッド、オブジェクト、スレッド、プロセスの場合があります)
バッファ: コンシューマーはプロデューサーのデータを直接使用できません。両者の間にはバッファがあります。
プロデューサは生成されたデータをバッファに入れ、コンシューマはバッファからデータを取り出します。

package com.example.multithreading.demo15_PC;

import java.beans.Customizer;

// 管程法
// 生产者,消费者,产品,缓冲区
public class PcTest {
    
    
    public static void main(String[] args) {
    
    
        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}

// 生产者
class Productor extends Thread{
    
    
    SynContainer container;

    public Productor(SynContainer container){
    
    
        this.container = container;
    }

    // 生产
    @Override
    public void run(){
    
    
        for(int i = 0; i < 100; i++){
    
    
            container.push(new Chicken(i));
            System.out.println("生产了->第" + i + "只鸡");
        }
    }
}

// 消费者
class Consumer extends Thread{
    
    
     SynContainer container;

     public Consumer(SynContainer container){
    
    
         this.container = container;
     }

    // 消费
    @Override
    public void run(){
    
    
         for(int i = 0; i < 100; i++){
    
    
             System.out.println("消费了---->第" + container.pop().id + "只鸡");
         }
    }
}

// 产品
class Chicken{
    
    
    // 产品编号
    int id;

    public Chicken(int id) {
    
    
        this.id = id;
    }
}

// 缓冲区
class SynContainer {
    
    

    // 需要一个容器大小
    Chicken[] chickens = new Chicken[10];

    // 容器计数器
    int count = 0;

    // 生产者放入产品
    public synchronized void push(Chicken chicken){
    
    
        // 如果容器满了,就需要等待消费者消费
        if (count == chickens.length){
    
    
            // 通知消费者消费,生产等待
            try {
    
    
                this.wait();
            }catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        // 如果没有满,我们就需要丢入产品
        chickens[count] = chicken;
        count++;

        // 可以通知消费者消费了
        this.notifyAll();
    }

    // 消费者消费产品
    public synchronized Chicken pop(){
    
    
        // 判断能否消费
        if (count == 0){
    
    
            // 等待生产者生产,消费者等待
            try{
    
    
                this.wait();
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }

        // 如果可以消费
        count--;
        Chicken chicken = chickens[count];

        // 吃完了,通知生产者生产
        this.notifyAll();

        return chicken;
    }


}

結果
ここに画像の説明を挿入

2.信号灯方式

フラグビットによって制御

package com.example.multithreading.demo15_PC;

// 信号灯法,标志位解决
public class PcTest2 {
    
    
    public static void main(String[] args) {
    
    
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

// 生产者 --> 演员
class Player extends Thread{
    
    
    TV tv;
    public Player(TV tv) {
    
    
        this.tv = tv;
    }

    @Override
    public void run() {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            if(i%2==0){
    
    
                this.tv.play("电视剧");
            } else{
    
    
                this.tv.play("电影");
            }
        }
    }
}

// 消费者 --> 观众
class Watcher extends Thread{
    
    
    TV tv;
    public Watcher(TV tv) {
    
    
        this.tv = tv;
    }
    @Override
    public void run() {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            tv.watch();
        }
    }
}

// 产品 --> 节目
class TV{
    
    
    // 演员表演,观众等待 T
    // 观众观看,演员等待 F
    // 表演的节目
    String voice;
    // 表演的节目
    boolean flag = true;

    // 表演
    public synchronized void play(String voice){
    
    
        if (!flag){
    
    
            try{
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        System.out.println("演员表演了:" + voice);
        // 通知观众观看
        // 通知唤醒
        this.notifyAll();
        this.voice = voice;
        this.flag = false;
    }

    // 观看
    public synchronized void watch(){
    
    
        if(flag){
    
    
            try{
    
    
                this.wait();
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }
        System.out.println("观看了:" + voice);
        // 通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

結果
ここに画像の説明を挿入

3. スレッドプール

3.1 背景

頻繁に作成および破棄され、大量のリソースを使用するリソース (同時状況のスレッドなど) は、パフォーマンスに大きな影響を与えます。

3.2 アイデア

事前に複数のスレッドを作成してスレッドプールに入れ、使用時に直接取得し、使用後はプールに戻します。頻繁な作成と破棄を回避し、再利用を実現できます。

3.3 利点

(1) 応答速度の向上 (新しいスレッドの作成にかかる時間を短縮します)
(2) リソース消費の削減 (スレッド プール内のスレッドを再利用します。毎回作成する必要はありません)
(3) スレッド管理の容易化
corePoolSize: コアのサイズプールの
maximumPoolSize: スレッドの最大数
keepAliveTime: タスクが存在しない場合、スレッドが終了するまでに最大でどのくらいの時間保持されますか

3.4 ExecutorService と Executor

ExecutorService: リアル スレッド プール インターフェイス (共通サブクラス ThreadPoolExecutor)
voidexecute(Runnable command): 通常 Runnable の実行に使用される、戻り値なしでタスク/コマンドを実行します。
Future submit(Callable task): タスクを実行し、戻り値を持ち、通常は Callable を実行するために使用されます。
void shutdown() : 接続プールを閉じます。
エグゼキュータ: ツール クラス、スレッド プールのファクトリ クラス。さまざまなタイプのスレッド プールを作成して返すために使用されます。

3.5 サンプル

package com.example.multithreading.demo16_Pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 测试线程池
public class PoolTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 1、创建服务,创建线程池
        // newFixedThreadPool 参数为:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        // 2、关闭链接
        service.shutdown();
    }
}

class MyThread implements Runnable {
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
}

結果
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_46106857/article/details/128204183