記事ディレクトリ
セマフォ、「セマフォ」に翻訳。最初にセマフォモデルを紹介し、次にセマフォの使用方法を紹介し、最後にセマフォを使用して電流リミッタを実装します。
セマフォモデル
カウンター、待機キュー、および3つのメソッドカウンターと待機キューは不透明で、3つのメソッドを介してのみアクセスできます。
- init()、カウンターの初期値を設定します。
- down():カウンターの値は1ずつ減少します。この時点でカウンターの値が0未満の場合、現在のスレッドはブロックされます。それ以外の場合、現在のスレッドは実行を継続できます。
- up():カウンターの値を1増やします;この時点でカウンターの値が0以下の場合、待機キュー内のスレッドを起動し、待機キューから削除します。
3つのメソッドinit()、down()、up()はアトミックであり、セマフォモデルの実装者によって保証されています。
class Semaphore {
// 计数器
int count;
// 等待队列
Queue queue;
// 初始化操作
Semaphore(int c) {
this.count = c;
}
//
void down() {
this.count--;
if (this.count < 0) {
// 将当前线程插入等待队列
// 阻塞当前线程
}
}
void up() {
this.count++;
if (this.count <= 0) {
// 移除等待队列中的某个线程 T
// 唤醒线程 T
}
}
}
Java SDKコンカレントパッケージでは、down()およびup()は、acquire()およびrelease()に対応します。
2.セマフォの使い方
次のコード:
static int count;
// 初始化信号量
static final Semaphore s = new Semaphore(1);
// 用信号量保证互斥
static void addOne() {
s.acquire();
try {
count += 1;
} finally {
s.release();
}
}
count + = 1操作はクリティカルセクションであり、1つのスレッドのみが実行を許可されます。クリティカルセクションに入る前にdown()操作を実行し、クリティカルセクションを終了する前にup()操作を実行してください。cquire()はセマフォのdown()操作であり、release()はセマフォのup()操作です。
2つのスレッドT1とT2が同時にaddOne()メソッドにアクセスするとします。acquire()を同時に呼び出すと、acquire()はアトミック操作であるため、1つのスレッド(T1と仮定)のみがセマフォのカウンターをデクリメントします。 0の場合、他のスレッド(T2)は、カウンターを-1に減らします。スレッドT1の場合、セマフォのカウンタ値は0、つまり0以上であるため、スレッドT1は実行を継続します。スレッドT2の場合、セマフォのカウンタ値は-1未満で、downのセマフォモデルに従って0未満です。 ()動作の説明、スレッドT2はブロックされます。したがって、現時点では、スレッドT1のみがクリティカルセクションに入り、count + = 1を実行します。
スレッドT1がrelease()操作、つまりup()操作を実行すると、セマフォのカウンタの値は-1になり、1を追加した後の値は0になり、0以下になります。セマフォモデルによれば、up()操作説明、この時点で待機キューのT2が起こされます。したがって、T2は、T1がクリティカルセクションコードを実行した後で実行のためにクリティカルセクションに入る機会を得ただけで、相互排除を確実にしました。
3.電流リミッターをすばやく実装する
セマフォには、ロックを実装するのが容易でない別の機能があります。つまり、セマフォは、複数のスレッドがクリティカルセクションにアクセスできるようにすることができます。
実際のより一般的な要件は、接続プール、オブジェクトプール、スレッドプールなど、作業中に遭遇するさまざまなプールされたリソースです。たとえば、データベース接続プールでは、複数のスレッドが同時に接続プールを使用できるようにする必要があります。もちろん、各接続は、解放されるまで他のスレッドによる使用を許可されていません。
現在のリミッターとは、N個を超えるスレッドが同時にクリティカルセクションに入ることを許可しないことを指します。
セマフォカウンターは1に設定され、1つのスレッドのみがクリティカルセクションに入ることが許可され、オブジェクトプール内のオブジェクトの数Nに設定され、Nスレッドがクリティカルセクションに入ることが許可されることを示します。
class ObjPool<T, R> {
final List<T> pool;
// 用信号量实现限流器
final Semaphore sem;
// 构造函数
ObjPool(int size, T t) {
pool = new Vector<T>() {
};
for (int i = 0; i < size; i++) {
pool.add(t);
}
sem = new Semaphore(size);
}
// 利用对象池的对象,调用 func
R exec(Function<T, R> func) {
T t = null;
sem.acquire();
try {
t = pool.remove(0);
return func.apply(t);
} finally {
pool.add(t);
sem.release();
}
}
}
// 创建对象池
ObjPool<Long, String> pool = new ObjPool<Long, String>(10, 2);
// 通过对象池获取 t,之后执行
pool.exec(t->
{
System.out.println(t);
return t.toString();
});
- Listを使用してオブジェクトインスタンスを格納し、Semaphoreを使用して現在のリミッターを実装します。
- 重要なコードは、現在の制限関数を実装するObjPoolのexec()メソッドです。
- このメソッドでは、最初に、acquire()メソッドを呼び出します(一致するメソッドは、最後にrelease()メソッドを呼び出すことです)。
- オブジェクトプールのサイズが10で、セマフォカウンターが10に初期化されている場合、acquire()メソッドを呼び出す最初の10スレッドは実行を継続できます。これは、セマフォを渡すことと同じですが、他のスレッドは、acquire()メソッドでブロックします。 。
- セマフォを通過するスレッドに対して、各スレッドにオブジェクトtを割り当てました(この割り当てはpool.remove(0)によって実現されます)。割り当て後、コールバック関数funcが実行され、関数のパラメーターは以前の割り当てですオブジェクトt;
- コールバック関数が実行された後、オブジェクトを解放し(この解放はpool.add(t)によって実現されます)、release()メソッドを呼び出してセマフォカウンターを更新します。
- この時点でセマフォのカウンタの値が0以下の場合は、待機中のスレッドがあることを意味し、待機中のスレッドはこの時点で自動的に起動されます。