マルチスレッド(エレメンタリーII)

スレッドセーフなシングルトンパターンを実装する

シングルトンパターンはデザインパターン
です。デザインパターンとは何ですか?(おそらくチェスの本として理解されており、多くのチェスルーチンが含まれています)
シングルトンパターン:
コード内の特定のクラスは、多くのインスタンスではなく、1つのインスタンスのみを持つ必要があります。
インスタンスはオブジェクトです。
つまり、クラスは複数のオブジェクトではなく、1つのオブジェクトのみを新規作成できます。
2つの典型的な実装があります:
1:空腹の男モード
人気のある例を挙げます。皿洗い
、正午の食事には4つのボウルが必要です。次に、食べた直後に4つのボウルを洗います。
2:レイジーモード
正午に食事をするためには、4つのボウルが必要です。食べた後に洗う必要はありません。夜の夕食には2つのボウルだけが必要です。その後、2つのボウルだけを洗います
。空腹の男性のシングルトンモードは、インスタンスを作成することをより望んでいます。 、
およびレイジーモードのシングルトンモードは、インスタンスの作成を急いでいませんが、使用時に実際にインスタンスを作成します
怠惰なモードは、コンピューターの空腹のモードよりも効率的です)。

空腹の男モードの実装

// 通过 Singleton 这个类来实现单例模式. 保证 Singleton 这个类只有唯一实例
// 饿汉模式
class Singleton {
    
    
    // 1. 使用 static 创建一个实例, 并且立即进行实例化.
    //    这个 instance 对应的实例, 就是该类的唯一实例.
    private static Singleton instance = new Singleton();
    // 2. 为了防止程序猿在其他地方不小心的 new 这个 Singleton, 就可以把构造方法设为 private
    private Singleton() {
    
    }
    // 3. 提供一个方法, 让外面能够拿到唯一实例.
    public static Singleton getInstance() {
    
      //仅仅是读取了变量的内容,如果多个线程只读同一个变量,不修改,此时仍然是线程安全的
        return instance;
    }
}

public class Demo19 {
    
    
    public static void main(String[] args) {
    
    
        Singleton instance = Singleton.getInstance();

        // Singleton instance2 = new Singleton();
    }
}

レイジーマンモードの実装

ロックはそこにあり、それを理解するためにそこにある場合はダブルです。

// 实现单例模式 - 懒汉模式
class Singleton2 {
    
    
    // 1. 就不是立即就初始化实例.
    private static volatile Singleton2 instance = null;
    // 2. 把构造方法设为 private
    private Singleton2() {
    
    }
    // 3. 提供一个方法来获取到上述单例的实例
    //    只有当真正需要用到这个 实例 的时候, 才会真正去创建这个实例.
    public static Singleton2 getInstance() {
    
    
        // 如果这个条件成立, 说明当前的单例未初始化过的, 存在线程安全风险, 就需要加锁~~
        if (instance == null) {
    
    
            synchronized (Singleton2.class) {
    
    
                if (instance == null) {
    
     //两个if一样只是一个美丽的巧合
                    instance = new Singleton2();//只有真正用到getInstance的时候才会真的创建实例     
                  
                }
            }
        }
        return instance;
    }
}

public class Demo20 {
    
    
    public static void main(String[] args) {
    
    
        Singleton2 instance = Singleton2.getInstance();
    }
}

ブロッキングキューを実装する

キュー:
先入れ先出しブロッキングキューは、先入れ先出しルールに準拠するキューでもあり、通常のキューとは少し異なります。
1:スレッドセーフ
2:ブロッキング効果を生み出す
(1)キューが空の場合は、キューから出てみてください。キューが空になるまでブロックされます。
(2)キューがいっぱいの場合は、キューに入ろうとします。キューがいっぱいではありません。

アピールの特性に基づいて、「生産者/消費者モデル」を実現できます。
ここでのブロッキングキューは「取引場所」と比較できます。
取引所
(旧正月にみんなが餃子を食べるのと同じように、多くの人の協力が必要になることが多く、大きく3点に分けられます(1:和面== 2:餃子の皮を広げます==3:餃子を調理する))
3人のABCが一緒に来ていると仮定します
1:3人全員がスキンバッグと餃子のロールをしている場合(ロックの競合の問題があります(通常、麺棒は1つだけです))
2:これは次の方法です:麺棒を専門とし、BとCは包装を専門とし、
Aは餃子の皮の生産者、BCは餃子の皮の消費者
、餃子の皮が置かれる「蓋カーテン」は「取引場所」の
ここに画像の説明を挿入生産者です。 -実際の開発で非常に一般的に使用されるモデルであるコンシューマーモデル。マルチスレッド開発方法!!!特にサーバー開発のシナリオで

シミュレーションの実装

class MyBlockingQueue {
    
    
    // 保存数据的本体
    private int[] data = new int[1000];
    // 有效元素个数
    private int size = 0;
    // 队首下标
    private int head = 0;
    // 队尾下标
    private int tail = 0;

    // 专门的锁对象
    private Object locker = new Object();

    // 入队列
    public void put(int value) throws InterruptedException {
    
    
        synchronized (locker) {
    
    
            if (size == data.length) {
    
    
                // 队列满了. 暂时先直接返回.
                // return;
                locker.wait();
            }
            // 把新的元素放到 tail 位置上.
            data[tail] = value;
            tail++;
            // 处理 tail 到达数组末尾的情况
            if (tail >= data.length) {
    
    
                tail = 0;
            }
            // tail = tail % data.length;
            size++;  // 千万别忘了. 插入完成之后要修改元素个数
            // 如果入队列成功, 则队列非空, 于是就唤醒 take 中的阻塞等待.
            locker.notify();
        }
    }

    // 出队列
    public Integer take() throws InterruptedException {
    
    
        synchronized (locker) {
    
    
            if (size == 0) {
    
    
                // 如果队列为空, 就返回一个非法值.
                // return null;
                locker.wait();
            }
            // 取出 head 位置的元素
            int ret = data[head];
            head++;
            if (head >= data.length) {
    
    
                head = 0;
            }
            size--;
            // take 成功之后, 就唤醒 put 中的等待.
            locker.notify();
            return ret;
        }
    }
}

public class Demo22 {
    
    
    private static MyBlockingQueue queue = new MyBlockingQueue();

    public static void main(String[] args) {
    
    
        // 实现一个简单的生产者消费者模型
        Thread producer = new Thread(() -> {
    
    
            int num = 0;
            while (true) {
    
    
                try {
    
    
                    System.out.println("生产了: " + num);
                    queue.put(num);
                    num++;
                    // 当生产者生产的慢一些的时候, 消费者就得跟着生产者的步伐走.
                    // Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        producer.start();

        Thread customer = new Thread(() -> {
    
    
            while (true) {
    
    
                try {
    
    
                    int num = queue.take();
                    System.out.println("消费了: " + num);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        customer.start();


        // 简单验证看这个队列是否能正确工作.
//        MyBlockingQueue queue = new MyBlockingQueue();
//        queue.put(1);
//        queue.put(2);
//        queue.put(3);
//        queue.put(4);
//        int ret = 0;
//        ret = queue.take();
//        System.out.println(ret);
//        ret = queue.take();
//        System.out.println(ret);
//        ret = queue.take();
//        System.out.println(ret);
//        ret = queue.take();
//        System.out.println(ret);
    }
}

ここに画像の説明を挿入

タイマー

目覚まし時計のように、一定時間後に計時され、ウェイクアップして以前に設定されたタスクを実行します
タイマーの構成:
優先度のあるブロッキングキューキュー内の各要素はタスク
オブジェクトです。
チームの最初の要素は、チーム
の最初の要素をスキャンして、チームの最初の要素を実行する必要があるかどうかを確認するために、ワーカースレッドを使用しようとしています。

import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;

// 创建一个类, 表示一个任务.
class MyTask implements Comparable<MyTask> {
    
    
    // 任务具体要干啥
    private Runnable runnable;
    // 任务具体啥时候干. 保存任务要执行的毫秒级时间戳
    private long time;

    // after 是一个时间间隔. 不是绝对的时间戳的值
    public MyTask(Runnable runnable, long delay) {
    
    
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    public void run() {
    
    
        runnable.run();
    }

    public long getTime() {
    
    
        return time;
    }

    @Override
    public int compareTo(MyTask o) {
    
    
        // 到底是谁见谁, 才是一个时间小的在前? 需要咱们背下来.
        return (int) (this.time - o.time);
    }
}

class MyTimer {
    
    
    // 定时器内部要能够存放多个任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long delay) {
    
    
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        // 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
        synchronized (locker) {
    
    
            locker.notify();
        }
    }

    private Object locker = new Object();

    public MyTimer() {
    
    
        Thread t = new Thread(() -> {
    
    
            while (true) {
    
    
                try {
    
    
                    // 先取出队首元素
                    MyTask task = queue.take();
                    // 再比较一下看看当前这个任务时间到了没?
                    long curTime = System.currentTimeMillis();
                    if (curTime < task.getTime()) {
    
    
                        // 时间没到, 把任务再塞回到队列中.
                        queue.put(task);
                        // 指定一个等待时间
                        synchronized (locker) {
    
    
                            locker.wait(task.getTime() - curTime);
                        }
                    } else {
    
    
                        // 时间到了, 执行这个任务
                        task.run();
                    }
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

public class Demo24 {
    
    
    public static void main(String[] args) {
    
    
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("hello timer!");
            }
        }, 3000);
        System.out.println("main");
    }
}

ここに画像の説明を挿入

スレッドプール

プロセスの頻繁な作成と破棄は比較的高価です。解決策:プロセス
よりもはるかに軽量ですが、プロセスプールまたはスレッドスレッドですが、頻繁な作成と破棄のコストも比較的高くなります。解決策:スレッドプールまたはコルーチン

スレッドプール:事前にスレッドを作成してプールに入れます~~後でスレッドが必要になったときに、プールから直接スレッドを取得できます。適用するためにシステムに移動する必要はなく、スレッドをに戻すだけです。使用後のプール

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class MyThreadPool {
    
    
    // 1. 描述一个任务. 直接使用 Runnable, 不需要额外创建类了.
    // 2. 使用一个数据结构来组织若干个任务.
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    // 3. 描述一个线程, 工作线程的功能就是从任务队列中取任务并执行.
    static class Worker extends Thread {
    
    
        // 当前线程池中有若干个 Worker 线程~~ 这些 线程内部 都持有了上述的任务队列.
        private BlockingQueue<Runnable> queue = null;

        public Worker(BlockingQueue<Runnable> queue) {
    
    
            this.queue = queue;
        }

        @Override
        public void run() {
    
    
            // 就需要能够拿到上面的队列!!
            while (true) {
    
    
                try {
    
    
                    // 循环的去获取任务队列中的任务.
                    // 这里如果队列为空, 就直接阻塞. 如果队列非空, 就获取到里面的内容~~
                    Runnable runnable = queue.take();
                    // 获取到之后, 就执行任务.
                    runnable.run();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
    // 4. 创建一个数据结构来组织若干个线程.
    private List<Thread> workers = new ArrayList<>();

    public MyThreadPool(int n) {
    
    
        // 在构造方法中, 创建出若干个线程, 放到上述的数组中.
        for (int i = 0; i < n; i++) {
    
    
            Worker worker = new Worker(queue);
            worker.start();
            workers.add(worker);
        }
    }

    // 5. 创建一个方法, 能够允许程序猿来放任务到线程池中.
    public void submit(Runnable runnable) {
    
    
        try {
    
    
            queue.put(runnable);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

public class Demo26 {
    
    
    public static void main(String[] args) {
    
    
        MyThreadPool pool = new MyThreadPool(10);
        for (int i = 0; i < 100; i++) {
    
    
            pool.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println("hello threadpool");
                }
            });
        }
    }
}

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/chenbaifan/article/details/123984293