同時:スレッドとロック

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つの主要な危害:
  • 競合条件
  • デッドロック
  • メモリ可視性
  • 回避の原則:
  • 共有変数へのすべてのアクセスを同期化する必要がある
  • リードスレッドとライトスレッドの両方を同期化する必要がある
  • は、所定のグローバルシーケンスに従って複数のロック
  • を取得する.
  • ロックを持つときに外星メソッド
  • を呼び出すことを避ける.
  • ロックを持つ時間はできるだけ短くしなければならない
  • 内蔵ロック


    組み込みロックの制限:
  • は、内蔵ロックを待つためにスレッドがブロックに入った後、スレッドを中断することができません.
  • がタイムアウトできずに内蔵ロックを取得しようとした場合、タイムアウトを設定できません.
  • は内蔵ロックを柔軟に入手できず、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);
        }
    }
    

    げんしへんすう


    原子変数はロックなし非ブロックアルゴリズムの基礎であり,このアルゴリズムはロックとブロックを用いずに同期の目的を達成できる.