2

質問:

  1. 最後の要素を削除しようとすると NoSuchElementException が発生するのはなぜですか?
  2. どうすれば修正できますか?

LinkedList に整数を追加/削除する 3 つのクラス (以下を参照) があります。削除するスレッドが最後の要素に到達するまで、すべてが正常に機能します。

両方のスレッドがそれを削除しようとしているようです。最初のものは成功しますが、2 番目のものはできません。しかし、私は synchronized-method/synchroniced-attribute +!sharedList.isEmpty()がそれを処理すると思っていました。

Class Producer: このクラスは、乱数を作成し、それらをsharedListに入れ、数値を追加したことをコンソールに書き込み、中断されると停止することになっています。このクラスのスレッドは 1 つだけ必要です。

import java.util.LinkedList;

    public class Producer extends Thread
    {

        private LinkedList sharedList;
        private String name;

        public Producer(String name, LinkedList sharedList)
        {
            this.name = name;
            this.sharedList = sharedList;
        }

        public void run()
        {
            while(!this.isInterrupted())
            {
                while(sharedList.size() < 100)
                {
                    if(this.isInterrupted())
                    {
                        break;
                    } else 
                    {
                        addElementToList();
                    }
                }
            }
        }

        private synchronized void addElementToList() 
        {
            synchronized(sharedList)
            {
                sharedList.add((int)(Math.random()*100));
                System.out.println("Thread " + this.name + ": " + sharedList.getLast() + " added");
            }
            try {
                sleep(300);
            } catch (InterruptedException e) {
                this.interrupt();
            }
        }
    }

クラス Consumer:このクラスは、sharedList に最初の要素が存在する場合、それを削除することになっています。実行は、 sharedListが空になるまで (中断された後) 続行する必要があります。このクラスの複数 (少なくとも 2 つ) のスレッドが必要です。

import java.util.LinkedList;

public class Consumer extends Thread
{
    private String name;
    private LinkedList sharedList;

    public Consumer(String name, LinkedList sharedList)
    {
        this.name = name;
        this.sharedList = sharedList;
    }

    public void run()
    {
        while(!this.isInterrupted())
        {
            while(!sharedList.isEmpty())
            {
                removeListElement();
            }
        }
    }

    private synchronized void removeListElement()
    {
        synchronized(sharedList)
        {
            int removedItem = (Integer) (sharedList.element());
            sharedList.remove();
            System.out.println("Thread " + this.name + ": " + removedItem + " removed");
        }
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            this.interrupt();
        }
    }
}

Class MainMethod:このクラスは、スレッドを開始および中断することになっています。

import java.util.LinkedList;


public class MainMethod 
{

    public static void main(String[] args) throws InterruptedException 
    {
        LinkedList sharedList = new LinkedList();
        Producer producer = new Producer("producer", sharedList);
        producer.start();
        Thread.sleep(1000);
        Consumer consumer1 = new Consumer("consumer1", sharedList);
        Consumer consumer2 = new Consumer("consumer2", sharedList);
        consumer1.start();
        consumer2.start();
        Thread.sleep(10000);
        producer.interrupt();
        consumer1.interrupt();
        consumer2.interrupt();
    }

}

例外:これはまさに私が受け取る例外です。

スレッド「Thread-2」での例外 java.util.LinkedList.getFirst(LinkedList.java:126) での java.util.NoSuchElementException java.util.LinkedList.element(LinkedList.java:476) での Consumer.removeListElement(Consumer. java:29) Consumer.run(Consumer.java:20) で

4

3 に答える 3

3

あなたの例外は説明がかなり簡単です。の

        while(!sharedList.isEmpty())
        {
            removeListElement();
        }

sharedList.isEmpty()同期外で発生するため、あるコンシューマーがリストを空として見ることができますが、別のコンシューマーはすでに最後の要素を取得しています。

空であると誤って信じた消費者は、もはやそこにない要素を削除しようとせず、クラッシュにつながります。

を使用してスレッドセーフにしたい場合は、すべての読み取り/書き込み操作をアトミックLinkedListに行う必要があります。例えば

while(!this.isInterrupted())
{
    if (!removeListElementIfPossible())
    {
        break;
    }
}

// method does not need to be synchronized - no thread besides this one is
// accessing it. Other threads have their "own" method. Would make a difference
// if this method was static, i.e. shared between threads.
private boolean removeListElementIfPossible()
{
    synchronized(sharedList)
    {
        // within synchronized so we can be sure that checking emptyness + removal happens atomic
        if (!sharedList.isEmpty())
        {
            int removedItem = (Integer) (sharedList.element());
            sharedList.remove();
            System.out.println("Thread " + this.name + ": " + removedItem + " removed");
        } else {
            // unable to remove an element because list was empty
            return false;
        }
    }
    try {
        sleep(1000);
    } catch (InterruptedException e) {
        this.interrupt();
    }
    // an element was removed
    return true;
}

プロデューサーにも同じ問題が存在します。しかし、彼らは110番目の要素かそのようなものを作成するだけです.

あなたの問題に対する良い解決策は、BlockingQueue. 例については、ドキュメントを参照してください。キューがすべてのブロックと同期を行うため、コードを心配する必要はありません。

編集: 2 つの while ループについて: 2 つのループを使用する必要はありません。そのため、キューを消費する前にキューに何かがあることを確認するか、他の方法でスレッドを手動で停止する必要があります。プロデューサーをthread.sleep(1000)開始した後はかなり安全ですが、1 秒後でもスレッドが実行されているとは限りません。CountDownLatch実際に安全にするには、eg a を使用します。

于 2014-02-17T10:10:33.017 に答える
0

場所を入れ替えてみる

while(!sharedList.isEmpty())

synchronized(sharedList)

removeListElement() で同期する必要はないと思います。

于 2014-02-17T10:11:13.897 に答える