目次
5.3 ハングリーマンモードとレイジーマンモードのスレッドセーフの問題
5.シングルトンモード
5.1 ハングリーマンモード
お腹が空いたので、何かを手に入れるとすぐに食べたいと思う空腹の男を理解してください. ここでは、クラスのロードのプロセスと唯一のインスタンスが直接作成されます.
長所: スレッドセーフの問題がない
短所: 怠け者モードとは異なり、呼び出し後にインスタンスが作成されます。これは、クラスがロードされたときにインスタンスが作成されるため、呼び出されていない場合はインスタンスを作成してリソースを浪費する必要があります。
//饿汉模式
//单例实体
class Singleton {
//唯一实例的本体
private static Singleton instance = new Singleton();
//获取到实例的方法
public static Singleton getInstance() {
return instance;
}
//禁止外部new实例,构造器私有化
private Singleton() {
}
}
public class Demo8 {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
}
}
5.2 レイジーパターン
怠け者は遅延ロードです. クラスがロードされるとき、インスタンスは作成されませんが、クラスが最初に呼び出されたときにインスタンスが作成されます.
必要なければ作成しない
//懒汉模式实现单例
class SingletonLazy {
//先置为空
private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
//只有调用getInstance方法时,才会去new
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy() {
}
}
public class Demo9 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
//s1 和 s2 指向的是同一个实例
System.out.println(s1 == s2);
}
}
5.3 ハングリーマンモードとレイジーマンモードのスレッドセーフの問題
明らかに、ハングリーマンモードの場合、変更操作がないため、マルチスレッドでスレッドセーフですが、レイジーマンモードでは、マルチスレッドでスレッドセーフの問題があります。インスタンスが空かどうかを確認してから、新しいオブジェクトの操作を実行します。スレッドの実行はプリエンプティブ プロセスであるため、ここでは遅延モードをロックする必要があります。
//懒汉模式实现单例 class SingletonLazy { //先置为空 volatile private static SingletonLazy instance = null; public static SingletonLazy getInstance() { //只有调用getInstance方法时,才会去new if (instance == null) { //加锁,保证线程安全问题 synchronized (SingletonLazy.class) { if (instance == null) { instance = new SingletonLazy(); } } } return instance; } private SingletonLazy() { } } public class Demo9 { public static void main(String[] args) { SingletonLazy s1 = SingletonLazy.getInstance(); SingletonLazy s2 = SingletonLazy.getInstance(); //s1 和 s2 指向的是同一个实例 System.out.println(s1 == s2); } }
ノート:
- インスタンスを volatile で変更する理由は、命令の並べ替えを禁止するためです。
- getInstance メソッドでは、このメソッドを直接ロックするのではなく、何らかの判断をしてからロックするかどうかを判断することで、プログラムの実行効率が向上します。
6.ブロッキングキュー
6.1 コンセプト
ブロッキング キューは特殊なキューで、「先入れ先出し」の原則に従います。
ブロッキング キューは、スレッド セーフなデータ構造にすることができ、次のプロパティがあります。
- キューがいっぱいになると、他のスレッドがキューから要素を取得するまでキューに入ることがブロックされます。
- キューが空の場合、キューを終了し続けると、他のスレッドが要素をキューに挿入するまでブロックされます。
ブロッキング キューの典型的なアプリケーション シナリオは "プロデューサー コンシューマー モデル" で、これは非常に典型的な開発モデルです。
6.2 生産者消費者モデル
概念: 2 つのプロセス (またはスレッド) A、B、および固定サイズのバッファーがあり、A プロセスがバッファーにデータを生成し、B プロセスが計算のためにバッファーからデータを取り出すと仮定します. これは単純なプロデューサー/コンシューマー モデルです. ここでの A プロセスは生産者に相当し、B プロセスは消費者に相当します。
プロデューサー コンシューマー モデルを使用する理由
マルチスレッド開発では、プロデューサーがデータを迅速に生成し、コンシューマーがデータをゆっくりと消費する場合、プロデューサーはデータを生成し続ける前にコンシューマーがデータを消費するのを待つ必要があります。同様に、コンシューマの速度がプロデューサの速度よりも速い場合、コンシューマは待機状態に対処することが多いため、プロデューサとコンシューマの生産データと消費データのバランスをとるために、生産者によって生成されたデータを保存するには、生産者と消費者のモデルが導入されます。
ここでのバッファの役割は簡単に言えば、プロデューサーとコンシューマーのデータ処理能力のバランスを取ることであり、一方ではキャッシングの役割を果たし、他方ではデカップリングの役割を果たします。
プロデューサー コンシューマー モデル コード:
//生产这消费者模型 public class Demo10 { public static void main(String[] args) { //阻塞队列 BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(); //消费者线程 Thread t1 = new Thread(()->{ while (true){ try { int value = blockingQueue.take(); System.out.println("消费元素:"+value); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); //生产者线程 Thread t2 = new Thread(()->{ int value = 0; while (true){ try { System.out.println("生产元素:"+value); blockingQueue.put(value); value++; Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t2.start(); } }
6.3 ブロッキングキューを実装するためのシミュレーション
次の 3 つの手順で実行できます。
- 最初に共通キューを実装します
- プラススレッドセーフ
- プラスブロッキング機能
//模拟实现一个阻塞队列 class MyBlockingQueue { private int[] array = new int[10000]; //[head,tail)之间为有效元素 volatile private int head = 0; volatile private int tail = 0; volatile private int size = 0; synchronized public void put(int elem) throws InterruptedException { //判断队列是否满了 if (size == array.length) { this.wait(); } array[tail] = elem; tail++; //判断tail是否达到末尾,如果达到了,把tail置为0,从头开始 if (tail == array.length) { tail = 0; } size++; this.notify(); } synchronized public Integer take() throws InterruptedException { //判断队列是否为空 if (size == 0) { this.wait(); } int value = array[head]; head++; if (head == array.length) { head = 0; } size--; this.notify(); return value; } }
7. タイマー
7.1 タイマーの概念
タイマーは、ソフトウェア開発においても重要なコンポーネントであり、「目覚まし時計」に似ており、設定された時間になると、指定されたコードが実行されます。
- 標準ライブラリには Timer クラスが用意されており、Timer クラスのコアメソッドはスケジュールです。
- schedule には 2 つのパラメーターが含まれています。最初のパラメーターは実行するタスク コードを指定し、2 番目のパラメーターは実行にかかる時間を指定します (ミリ秒単位)。
//实现一个定时器,先执行任务2,等待2s后再执行任务1 public class Demo12 { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("任务1"); } },2000);//等待2s后再执行任务1 System.out.println("任务2"); } }
7.2 アナログ実現タイマー
//模拟实现一个定时器 //表示一个任务 class Mytask implements Comparable<Mytask> { public Runnable runnable; public long time; public Mytask(Runnable runnable, long delay) { this.runnable = runnable; //当前时刻的时间戳+定时器参数列表的时间 this.time = System.currentTimeMillis() + delay; } @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,插入到队列 Mytask mytask = new Mytask(runnable, delay); queue.put(mytask); synchronized (locker) { locker.notify(); } } //创建锁对象 private Object locker = new Object(); //创建线程,执行任务 public MyTimer() { Thread t = new Thread(() -> { while (true) { try { synchronized (locker) { Mytask mytask = queue.take(); //获取当前时间 long curTime = System.currentTimeMillis(); //判断时间是否到了 if (mytask.time <= curTime) { mytask.runnable.run(); } //时间没到 else { //取出当前执行的任务,重新塞回阻塞队列中 queue.put(mytask); locker.wait(mytask.time - curTime); } } } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); } } public class Demo13 { public static void main(String[] args) { MyTimer myTimer = new MyTimer(); myTimer.schedule(new Runnable() { @Override public void run() { System.out.println("任务1"); } }, 1000); myTimer.schedule(new Runnable() { @Override public void run() { System.out.println("任务2"); } }, 1000); myTimer.schedule(new Runnable() { @Override public void run() { System.out.println("任务3"); } }, 1000); System.out.println("最开始的任务"); } }
8.スレッドプール
スレッドプールは、主にスレッドを再利用し、スレッドとタスクを便利に管理し、タスクの実行からスレッドの作成を切り離すために、プーリング技術のアイデアを使用して実装されたスレッド管理技術です。スレッド プールを作成して、作成されたスレッドを再利用し、スレッドの頻繁な作成と破棄によるリソース消費を削減できます。
8.1 工法パラメータの分析
ThreadPoolExecutor の構築方法のパラメータ分析に関する jdk1.8 公式ドキュメント:
corePoolSize: コアスレッド数 (企業の正社員に相当);
maximumPoolSize: 最大スレッド数 (企業の正社員 + インターンに相当);
現在のタスクが多数ある場合、スレッド プールは、タスクの解決に役立つ「一時スレッド」をさらに作成します。
タスクが少ない場合、スレッド プール内の余分な「一時スレッド」は破棄されますが、コア スレッドの数は破棄されません。
long keepAliveTime: 「一時スレッド」の最大生存時間を示します。この時間を超えると、スレッドは破棄されます。
TimeUnit unit: 値の単位です。
BlockingQueue<Runnable>: ブロッキング キュー。
ThreadFactroy threadFactory: スレッドの作成に使用されるスレッド ファクトリ。
RejectedExecutionHandler ハンドラー: スレッド プールの拒否戦略; スレッド プールがいっぱいの場合、拒否戦略をトリガーするタスクをスレッド プールに追加し続けます。
8.2 スレッドプールのシミュレーション実装
class MyThreadPool {
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
try {
while (true) {
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
}
public class Demo15 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int ret = i;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("任务 " + ret);
}
});
}
}
}