javamultithreading

How to do Java multithreading properly


As shown in the below code snippet, there is a station and there are two trains. Station is a thread, and trains are two different threads.Station thread adds people to the station and train threads remove people from the station.

ThreadGroup group = new ThreadGroup("MyThreadGroup");
MainStation mainStation = new MainStation();
Thread stationThread= new Thread(group,mainStation);
TrainA trainA = new TrainA(mainStation);
TrainB trainB=new TrainB(mainStation);
Thread trainAThread=new Thread(group,trainA);
Thread trainBThread=new Thread(group,trainB);

stationThread.setDaemon(true);

ExecutorService executorService= Executors.newFixedThreadPool(3);
executorService.submit(stationThread);
executorService.submit(trainAThread);
executorService.submit(trainBThread);
executorService.shutdown();

When I run the program, only one train thread executes with the station thread and not both the train threads.

Main stattion
Train a running
Train B running
TrainA Removing people from station value--0
TrainB Removing people from station value--0
Station adding people to queue value--0 size--1
TrainA Queue size after removing --0
TrainA Removing people from station value--1
Station adding people to queue value--1 size--0
TrainA Queue size after removing --0
Station adding people to queue value--2 size--1
TrainA Removing people from station value--2
TrainA Queue size after removing --0
Station adding people to queue value--3 size--1
TrainA Removing people from station value--3
TrainA Queue size after removing --0
Station adding people to queue value--4 size--1
TrainA Removing people from station value--4
TrainA Queue size after removing --0
Station adding people to queue value--5 size--1
TrainA Removing people from station value--5
TrainA Queue size after removing --0
Station adding people to queue value--6 size--1
TrainA Removing people from station value--6
TrainA Queue size after removing --0
Station adding people to queue value--7 size--1
TrainA Removing people from station value--7
TrainA Queue size after removing --0
Station adding people to queue value--8 size--1
TrainA Removing people from station value--8
TrainA Queue size after removing --0
Station adding people to queue value--9 size--1
TrainA Removing people from station value--9
TrainA Queue size after removing --0

If you observe the above log, only train A is removing people from station and not train B.

Station

public class MainStation implements Runnable{

    private volatile ConcurrentLinkedDeque queue=new ConcurrentLinkedDeque();
    private final static People people=new People(10);

    @Override
    public void run() {
        synchronized(this){
            for (int i = 0; i < people.getCount(); i++) {
                //System.out.println("Station adding people to queue "+i);
                this.queue.add(i);
                System.out.println("Station adding people to queue value--" + i + " size--" + this.queue.size());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }

    public ConcurrentLinkedDeque getQueue() {
        return queue;
    }

}

Trains A and B

public class TrainA  implements Runnable{

    private MainStation station=null;

    public TrainA(MainStation station){
        this.station=station;
    }

    @Override
    public void run() {
        System.out.println("Train a running");
        while (true) {
            for (int i = 0; i < station.getQueue().size(); i++) {
                System.out.println("TrainA Removing people from station value--" + station.getQueue().getFirst());
                if(station!=null&&station.getQueue()!=null)station.getQueue().removeFirst();
                System.out.println("TrainA Queue size after removing --" + station.getQueue().size());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

Solution

  • The problem is that your code in TrainA and TrainB is not thread safe.

    Two threads can detect that the queue is not empty (A) and print out the first element (B). But after that only one thread can remove that element from the queue (C) - the other one sees an empty queue, station.getQueue().removeFirst(); throw a NoSuchElementException and that thread gets killed.

        for (int i = 0; i < station.getQueue().size(); i++) { // A
            System.out.println("TrainA Removing people from station value--" + station.getQueue().getFirst());  // B
            station.getQueue().removeFirst(); // C
    

    That this happens is visible in the output you provide (which happens when both threads reach line B):

    TrainA Removing people from station value--0
    TrainB Removing people from station value--0
    

    -- How can you improve your code?

    That depends on your goal.