Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing to/Reading from a Vector (or ArrayList) with two threads

I have two threads both of which accesses an Vector. t1 adds a random number, while t2 removes and prints the first number. Below is the code and the output. t2 seems to execute only once (before t1 starts) and terminates forever. Am I missing something here? (PS: Tested with ArrayList as well)

import java.util.Random;
import java.util.Vector;

public class Main {

public static Vector<Integer> list1 = new Vector<Integer>();

public static void main(String[] args) throws InterruptedException {
    System.out.println("Main started!");

    Thread t1 = new Thread(new Runnable() {

        @Override
        public void run() {
            System.out.println("writer started! "); 
            Random rand = new Random();

            for(int i=0; i<10; i++) {
                int x = rand.nextInt(100);
                list1.add(x);
                System.out.println("writer: " + x);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }               
        }

    });

    Thread t2 = new Thread(new Runnable() {

        @Override
        public void run() {
            System.out.println("reader started! ");         
            while(!list1.isEmpty()) {

                int x = list1.remove(0);
                System.out.println("reader: "+x);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }               
        }

    });


    t2.start();
    t1.start();

    t1.join();
    t2.join();      
}

} Output: Main started! reader started! writer started! writer: 40 writer: 9 writer: 23 writer: 5 writer: 41 writer: 29 writer: 72 writer: 73 writer: 95 writer: 46

like image 985
erol yeniaras Avatar asked Sep 27 '22 09:09

erol yeniaras


1 Answers

This sounds like a toy to understand concurrency, so I didn't mention it before, but I will now (at the top because it is important).

If this is meant to be production code, don't roll your own. There are plenty of well implemented (debugged) concurrent data structures in java.util.concurrent. Use them.


When consuming, you need to not shutdown your consumer based on "all items consumed". This is due to a race condition where the consumer might "race ahead" of the producer and detect an empty list only because the producer hasn't yet written the items for consumption.

There are a number of ways to accomplish a shutdown of the consumer, but none of them can be done by looking at the data to be consumed in isolation.

My recommendation is that the producer "signals" the consumer when the producer is done producing. Then the consumer will stop when it has both the "signal" no more data is being produced AND the list is empty.

Alternative techniques include creating a "shutdown" item. The "producer" adds the shutdown item, and the consumer only shuts down when the "shutdown" item is seen. If you have a group of consumers, keep in mind that you shouldn't remove the shutdown item (or only one consumer would shutdown).

Also, the consumer could "monitor" the producer, such that if the producer is "alive / existent" and the list is empty, the consumer assumes that more data will become available. Shutdown occurs when the producer is dead / non-existent AND no data is available.

Which technique you use will depend on the approach you prefer and the problem you're trying to solve.


I know that people like the elegant solutions, but if your single producer is aware of the single consumer, the first option looks like.

public class Producer {

   public void shutdown() {
      addRemainingItems();
      consumer.shutdown();
   }
}

where the Consumer looks like {

public class Consumer {

   private boolean shuttingDown = false;

   public void shutdown() {
     shuttingDown = true;
   }

   public void run() {
     if (!list.isEmpty() && !shuttingDown) {
        // pull item and process
     }
   }
}

Note that such lack of locking around items on the list is inherently dangerous, but you stated only a single consumer, so there's no contention for reading from the list.

Now if you have multiple consumers, you need to provide protections to assure that a single item isn't pulled by two threads at the same time (and need to communicate in such a manner that all threads shutdown).

like image 75
Edwin Buck Avatar answered Sep 29 '22 05:09

Edwin Buck