同時:スレッドとロック
11680 ワード
スレッドとロック
哲学者問題
5人の哲学者は1つの円卓をめぐって、机の上に2人の哲学者の間に箸を置いています.哲学者の状態は「思考」か「飢餓」かもしれない.飢えていると、哲学者は両側の箸を持ってしばらく食事をします.食事が終わると、哲学者は箸を返します.
コード実装:public class Philosopher extends Thread {
private Chopstick left;
private Chopstick right;
private Random random;
public Philosopher(Chopstick left, Chopstick right) {
this.left = left;
this.right = right;
random = new Random();
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000)); //
synchronized (left) { //
synchronized (right) { //
Thread.sleep(random.nextInt(1000)); //
}
}
}
} catch (InterruptedException e) {
// handle exception
}
}
}
回避方法:1つのスレッドが複数のロックを使用する場合、デッドロックの可能性を考慮する必要があります.幸いなことに、常にグローバルな固定された順序で複数のロックを取得すれば、デッドロックを避けることができます.public class Philosopher2 extends Thread {
private Chopstick first;
private Chopstick second;
private Random random;
public Philosopher2(Chopstick left, Chopstick right) {
if (left.getId() < right.getId()) {
first = left;
second = right;
} else {
first = right;
second = left;
}
random = new Random();
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000)); //
synchronized (first) { //
synchronized (second) { //
Thread.sleep(random.nextInt(1000)); //
}
}
}
} catch (InterruptedException e) {
// handle exception
}
}
}
スターメソッド
定義:このようなメソッドを呼び出すと、呼び出し元はメソッドの実装の詳細を理解しません.public class Downloader extends Thread {
private InputStream in;
private OutputStream out;
private ArrayList listeners;
public Downloader(URL url, String outputFilename) throws IOException {
in = url.openConnection().getInputStream();
out = new FileOutputStream(outputFilename);
listeners = new ArrayList<>();
}
public synchronized void addListener(ProgressListener listener) {
listeners.add(listener);
}
public synchronized void removeListener(ProgressListener listener) {
listeners.remove(listener);
}
private synchronized void updateProgress(int n) {
for (ProgressListener listener : listeners) {
listener.onProgress(n);
}
}
@Override
public void run() {
// ...
}
}
ここでupdateProgress(n)
メソッドは、別のロックを持つなど、何でもできる外星メソッドを呼び出します.
次のように変更できます.private void updateProgress(int n) {
ArrayList listenersCopy;
synchronized (this) {
listenersCopy = (ArrayList) listeners.clone();
}
for (ProgressListener listener : listenersCopy) {
listener.onProgress(n);
}
}
スレッドとロックモデルがもたらす3つの主要な危害:
public class Philosopher extends Thread {
private Chopstick left;
private Chopstick right;
private Random random;
public Philosopher(Chopstick left, Chopstick right) {
this.left = left;
this.right = right;
random = new Random();
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000)); //
synchronized (left) { //
synchronized (right) { //
Thread.sleep(random.nextInt(1000)); //
}
}
}
} catch (InterruptedException e) {
// handle exception
}
}
}
public class Philosopher2 extends Thread {
private Chopstick first;
private Chopstick second;
private Random random;
public Philosopher2(Chopstick left, Chopstick right) {
if (left.getId() < right.getId()) {
first = left;
second = right;
} else {
first = right;
second = left;
}
random = new Random();
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000)); //
synchronized (first) { //
synchronized (second) { //
Thread.sleep(random.nextInt(1000)); //
}
}
}
} catch (InterruptedException e) {
// handle exception
}
}
}
public class Downloader extends Thread {
private InputStream in;
private OutputStream out;
private ArrayList listeners;
public Downloader(URL url, String outputFilename) throws IOException {
in = url.openConnection().getInputStream();
out = new FileOutputStream(outputFilename);
listeners = new ArrayList<>();
}
public synchronized void addListener(ProgressListener listener) {
listeners.add(listener);
}
public synchronized void removeListener(ProgressListener listener) {
listeners.remove(listener);
}
private synchronized void updateProgress(int n) {
for (ProgressListener listener : listeners) {
listener.onProgress(n);
}
}
@Override
public void run() {
// ...
}
}
private void updateProgress(int n) {
ArrayList listenersCopy;
synchronized (this) {
listenersCopy = (ArrayList) listeners.clone();
}
for (ProgressListener listener : listenersCopy) {
listener.onProgress(n);
}
}
内蔵ロック
組み込みロックの制限:
synchronized
ブロックを使用する必要があります.synchronized( object ) {
<>
}
ReentrantLock
これは、上記の内蔵ロックのいくつかの制限を突破できる明示的な
lock
およびunlock
を提供する.ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
<>
} finally {
lock.unlock()
}
割り込み可能
組み込みロックを使用すると、ブロックされたスレッドが中断されないため、プログラムがデッドロックから回復することはできません.
内蔵ロック:デッドロックを作成するには:
public class Uninterruptible {
public static void main(String[] args) throws InterruptedException {
final Object o1 = new Object();
final Object o2 = new Object();
Thread t1 = new Thread(){
@Override
public void run() {
try {
synchronized (o1) {
Thread.sleep(1000);
synchronized (o2) {}
}
} catch (InterruptedException e) {
System.out.println("Thread-1 interrupted");
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
try {
synchronized (o2) {
Thread.sleep(1000);
synchronized (o1) {}
}
} catch (InterruptedException e) {
System.out.println("Thread-2 interrupted");
}
}
};
t1.start();
t2.start();
Thread.sleep(2000);
t1.interrupt();
t2.interrupt();
t1.join();
t2.join();
}
}
ReentrantLockは内蔵ロックの代わりに:
public class Interruptible {
public static void main(String[] args) {
final ReentrantLock lock1 = new ReentrantLock();
final ReentrantLock lock2 = new ReentrantLock();
Thread t1 = new Thread(){
@Override
public void run() {
try {
lock1.lockInterruptibly();
Thread.sleep(1000);
lock2.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("Thread-1 interrupted");
}
}
};
// ...
}
}
タイムアウト可能
ReentrantLockタイムアウト設定を利用して哲学者の問題を解決する:
public class Philosopher3 extends Thread {
private ReentrantLock leftChopstick;
private ReentrantLock rightChopstick;
private Random random;
public Philosopher3(ReentrantLock leftChopstick, ReentrantLock rightChopstick) {
this.leftChopstick = leftChopstick;
this.rightChopstick = rightChopstick;
random = new Random();
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000)); //
leftChopstick.lock();
try {
//
if (rightChopstick.tryLock(1000, TimeUnit.MILLISECONDS)) {
try {
Thread.sleep(random.nextInt(1000));
} finally {
rightChopstick.unlock();
}
} else {
// ,
}
} finally {
leftChopstick.unlock();
}
}
} catch (InterruptedException e) {
// ...
}
}
}
こうごロック
シーン:チェーンテーブルにノードを挿入する場合、チェーンテーブル全体をロックで保護するのではなく、交互ロックを使用してチェーンテーブルの一部だけをロックします.
スレッドセキュリティチェーン:
public class ConcurrentSortedList { //
private class Node {
int value;
Node pre;
Node next;
ReentrantLock lock = new ReentrantLock();
Node() {}
Node(int value, Node pre, Node next) {
this.value = value;
this.pre = pre;
this.next = next;
}
}
private final Node head;
private final Node tail;
public ConcurrentSortedList() {
this.head = new Node();
this.tail = new Node();
this.head.next = tail;
this.tail.pre = head;
}
public void insert(int value) {
Node current = this.head;
current.lock.lock();
Node next = current.next;
try {
while (true) {
next.lock.lock();
try {
if (next == tail || next.value < value) {
Node newNode = new Node(value, current, next);
next.pre = newNode;
current.next = newNode;
return;
}
} finally {
current.lock.unlock();
}
current = next;
next = current.next;
}
} finally {
next.lock.unlock();
}
}
public int size() {
Node current = tail; // ?
int count = 0;
while (current != head) {
ReentrantLock lock = current.lock;
lock.lock();
try {
++count;
current = current.pre;
} finally {
lock.unlock();
}
}
return count;
}
}
じょうけんへんすう
同時プログラミングは、条件が満たされるのを待つことが多い.たとえば、キューから要素を削除するには、キューが空でないのを待つ必要があります.キャッシュにデータを追加する前に、キャッシュを待つのに十分なスペースが必要です.
条件変数モード:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newConditiion();
lock.lock();
try {
while(!<>) { //
condition.await();
}
<>
} finnally {
lock.unlock();
}
1つの条件変数はロックに関連付ける必要があり、スレッドは待機条件を開始する前にロックを取得する必要があります.ロックを取得すると、スレッドは待機条件が真であるかどうかを確認します.
await()
を呼び出し、
をロック解除し、待機条件をブロックします.別のスレッドが
signal()
またはsignalAll()
を呼び出すと、対応する条件が真になる可能性があることを意味し、await()
は原子の回復を実行し、再ロックする.条件変数は哲学者の食事問題を解決する.
public class Philosopher4 extends Thread {
private boolean eating;
private Philosopher4 left;
private Philosopher4 right;
private ReentrantLock table;
private Condition condition;
private Random random;
public Philosopher4(ReentrantLock table) {
this.eating = false;
this.table = table;
this.condition = table.newCondition();
this.random = new Random();
}
public void setLeft(Philosopher4 left) {
this.left = left;
}
public void setRight(Philosopher4 right) {
this.right = right;
}
@Override
public void run() {
try {
while (true) {
think();
eat();
}
} catch (InterruptedException e) {
// ...
}
}
private void think() throws InterruptedException {
this.table.lock();
try {
this.eating = false;
this.left.condition.signal();
this.right.condition.signal();
} finally {
table.unlock();
}
Thread.sleep(1000);
}
private void eat() throws InterruptedException {
this.table.lock();
try {
while (left.eating || right.eating) {
this.condition.await();
}
this.eating = true;
} finally {
this.table.unlock();
}
Thread.sleep(1000);
}
}
げんしへんすう
原子変数はロックなし非ブロックアルゴリズムの基礎であり,このアルゴリズムはロックとブロックを用いずに同期の目的を達成できる.