0

I'm having a hard time understanding multithreading. Unfortunately, this is one of the assignments I need to submit in order to pass the course.

It's about a train: -The train waits for the Passenger Thread to send some passengers until the capacity is reached.

  • Then the train goes for a ride. During this period, no passengers can board the train.

  • The next step is unboarding, this is a procedure called by the Passenger thread.

  • Once this happens, the cycle goes on with the rest of passengers.

I'm having trouble in the unboarding part, sometimes I get an exception for array out of bounds.

Here's the error:

Passenger 3 has boarded the train
Passenger 0 has boarded the train
Passenger 1 has boarded the train
Passenger 12 has boarded the train
Passenger 13 has boarded the train
TRAIN FULL
SEAT: 0 Passenger: 3
SEAT: 1 Passenger: 0
SEAT: 2 Passenger: 1
SEAT: 3 Passenger: 12
SEAT: 4 Passenger: 13
RIDE STARTS
RIDE ENDS
Passenger 3 wants to get off the train. SEAT: 0
Passenger: 3 off the train
Passenger(s) left: 0
Passenger(s) left: 1
Exception in thread "Thread-16" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.elementData(Unknown Source)
at java.util.ArrayList.remove(Unknown Source)
at parque.Train.unboardTrain(Train.java:104)
at parque.Passenger.run(Passenger.java:23)
Passenger(s) left: 12
Passenger(s) left: 13
Passenger 15 wants to get off the train. SEAT: -1 //There is no passengerID 15, huh?

I would like to know how can I avoid this exception?, I was thinking maybe implementing another lock separate from the train lock, that would be in charge of the doors, or maybe this should be implemented as a condition?, help please

Here's the code:

public class Train extends Thread {
    private int id;
    private int capacity;
    private ArrayList<Integer> passengers;
    private Lock l = new ReentrantLock();
    private Condition trainFull = l.newCondition();
    private Condition boardTrain = l.newCondition();
    private Condition UnboardTrain = l.newCondition();
    private boolean canBoard = true;
    private boolean canUnboard = false;



//se definen los constructores  
    public Train(int id, int capacity) {
        this.id = id;
        this.capacity = capacity;
        this.passengers = new  ArrayList<Integer>(capacity);

    }//fin constructor  
    public Train(int id) {
        this.id = id;
        this.capacity = 5;
        passengers = new  ArrayList<Integer>(capacity);

    }//fin constructor

    public void boardTrain(int passengerId)  {
        l.lock();

        try{
            while(!canBoard)
                boardTrain.await();
                if (passengers.size() == capacity) {
                    canBoard = false;
                    trainFull.signal();
                } else {
                    passengers.add(passengerId);
                    System.out.println("Passenger " + passengerId +" has boarded the train");
                }//if

        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("Exception at boarding");
        }finally{
            l.unlock();
        }//try


    }//fin subir

    public void waitsFullTrain() {     //waits until n (capacity) passengers board the train
        l.lock();

        try{
            trainFull.await();

            System.out.println("TRAIN FULL");
            for(int i = 0; i< passengers.size(); i++){
                System.out.println("            SEAT: " + i + " Passenger: " + passengers.get(i));
            }//for
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            l.unlock();
        }//try


    }//fin esperaLleno

    public void goForRide() throws InterruptedException{

        l.lock();
        try{
            System.out.println("RIDE STARTS");
            Thread.sleep(2000);
            System.out.println("RIDE ENDS");
            canUnboard = true;
            UnboardTrain.signalAll();       

        }finally{
            l.unlock();
        }       

    }//fin darVuelta


    public void unboardTrain(int pasajeroId) {
        l.lock();

        try{
            while(!canUnboard)
                UnboardTrain.await();
            //System.out.println("Bajando..");
            if(passengers.size() >0){
                System.out.println("Passenger "+ pasajeroId + " wants to get off the train. SEAT: "+passengers.indexOf(pasajeroId) );
                passengers.remove(passengers.indexOf(pasajeroId));
                System.out.println("    Passenger: " + pasajeroId + " off the train");

                for (int i = 0; i<passengers.size();i++){
                    System.out.println("            Passenger(s) left: "+passengers.get(i));
                }
            }else{
                System.out.println();
                canUnboard = false;
                canBoard = true;
                boardTrain.signalAll();         
            }//if
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("Exception at unboarding");
        }finally{
            l.unlock();
        }//try


    }//fin bajar



    public int id() {
        return id;
    }//fin id

    @Override
    public void run() {
        while(true){
            this.waitsFullTrain();
            try {
                this.goForRide();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }//fin while    

    }//fin run


}//fin clase


public class Passenger extends Thread{

    private int id;
    private Train t;

    public Passenger(int id, Train t) {
        this.id = id;
        this.t = t;
    }

    @Override
    public void run() {

        t.boardTrain(this.id);
        t.unboardTrain(this.id);

    }//run
}//Passenger


public class Main {

    public static void main(String[] args) {

        Train t = new Train(1);
        Passenger[] p = new Passenger[20];

        for (int i = 0; i < p.length; i++) {
            p[i]= new Passenger(i, t);
        }//for
        t.start();

        for (int i = 0; i < p.length; i++) {
            p[i].start();
        }//for

        try {
            t.join();
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        for (int i = 0; i < p.length; i++) {
            try {
                p[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }//for
    }//main
}//clase
  • Please post the stacktrace. – M. Shaw Aug 12 '15 at 10:04
  • Model looks a little wrong in this design to me. Think about the real world... Passengers can't get on or off the train when either the doors are closed or the train is not in a station (those should be your locks). When doors are open, unboard(), board()... When timeout occurs, close doors & train departs. – Dave Aug 12 '15 at 11:42
  • You forgot to ask a question! It really is important that you ask an actual, specific question. That's why the button you pushed is labeled, `Ask Question`. – David Schwartz Aug 12 '15 at 15:38
  • Thanks Dave, I will try and use your suggestion. – remi.moncayo Aug 12 '15 at 16:51
  • Sorry David, already corrected that, cheers – remi.moncayo Aug 12 '15 at 16:51

2 Answers2

0

The problem is there are some passengers trying to unboard the train when they are really not inside it. Look at your code in 'boardTrain' and include the following change for a better understanding of your problem (see the new message when a passenger was unable to get into the train):

if (passengers.size() == capacity) {
    System.out.println("Passenger " + passengerId + " CANNOT board the train =>  TRAIN FULL");
    canBoard = false;
    trainFull.signal();
} else {
    passengers.add(passengerId);
    System.out.println("Passenger " + passengerId +" has boarded the train");
}

Now execute your code a few times. When you get the error again, you will see which passenger did not make it into the train (you can also see it in your trace because there is no any message regarding passenger 15 getting into the train).

What happens next? After boarding the the train, passenger try to unboard it:

t.boardTrain(this.id);
t.unboardTrain(this.id);

but what happens if a passenger which didn't get into the train tries to unboard? You will get your message like:

Passenger 15 wants to get off the train. SEAT: -1

and then the code goes on and executes the following line:

passengers.remove(passengers.indexOf(pasajeroId));

and the exception is thrown because 'passengers.indexOf(pasajeroId)' value is -1, and as you can read in documentation, -1 is not a valid index: http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html#remove(int)

Try to return a boolean with your 'boardTrain' method and only unboard in case the passenger made it to the train.

I hope this helps ;). Suerte.

BitExodus
  • 749
  • 6
  • 10
  • The error you pointed out is correct, but the solution should be, that the passenger should wait, until he can actually board the train. – xeed Aug 12 '15 at 15:21
  • BitExodus thanks a lot for pointing that out, I didn't think about those passengers who couldn't board the train. Unfortunately can't change the return type but will try to figure out a way in which only those who boarded the train can unboard, gracias! – remi.moncayo Aug 12 '15 at 16:54
  • @xeed that is an option, I suppose it depends on the problem definition. The passenger could wait to get in the train or leave the station, who knows. Fair enough. – BitExodus Aug 13 '15 at 06:48
0

Ok, so after playing around with this thing, here's the correct implementation in case anyone might need it:

In the end, the trick was checking if the train is full RIGHT AFTER a passenger has boarded. I was allowing the next passenger into the boardTrain method, then check the capacity, if full, reject that passenger, but that passenger got "lost", that's why I had the little problem in the unboard method and ArrayIndexOutOfBoundsException: -1.

public class Train extends Thread {
    private int id;
    private int capacity;
    private ArrayList<Integer> passengers;
    private Lock l = new ReentrantLock();
    private Condition trainFull = l.newCondition();
    private Condition boardTrain = l.newCondition();
    private Condition unboardTrain = l.newCondition();
    private boolean canBoard = true;
    private boolean canUnboard = false;



//se definen los constructores  
    public Train(int id, int capacity) {
        this.id = id;
        this.capacity = capacity;
        this.passengers = new  ArrayList<Integer>(capacity);

    }//fin constructor  
    public Train(int id) {
        this.id = id;
        this.capacity = 5;
        passengers = new  ArrayList<Integer>(capacity);

    }//fin constructor

    public void boardTrain(int passengerId)  {
        l.lock();

        try{
            while(!canBoard)
                boardTrain.await();
            passengers.add(passengerId);
            System.out.println("Passenger: " + passengerId +" has boarded the train");

            if (passengers.size() == capacity) {  //<------this here did the trick!
                canBoard = false;
                trainFull.signalAll();
            }


        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("Exception at boarding");
        }finally{
            l.unlock();
        }//try


    }//fin subir

    public void waitsFullTrain() {     //waits until n (capacity) passengers board the train
        l.lock();

        try{
            trainFull.await();
                        System.out.println("TRAIN FULL");
            for(int i = 0; i< passengers.size(); i++){
                System.out.println("            SEAT: " + i + " Passenger: " + passengers.get(i));
            }//for
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            l.unlock();
        }//try


    }//fin esperaLleno

    public void goForRide() throws InterruptedException{

        l.lock();
        try{
            System.out.println("RIDE STARTS");
            Thread.sleep(2000);
            System.out.println("RIDE ENDS");
            canUnboard = true;
            unboardTrain.signal();      

        }finally{
            l.unlock();
        }       

    }//fin darVuelta


    public void unboardTrain(int pasajeroId) {
        l.lock();

        try{
            while(!canUnboard)
                unboardTrain.await();
            //System.out.println("Bajando..");

            if(passengers.size() >0){
                if(passengers.indexOf(pasajeroId) > -1){
                    System.out.println("Passenger "+ pasajeroId + " wants to get off the train. SEAT: "+passengers.indexOf(pasajeroId) );
                    passengers.remove(passengers.indexOf(pasajeroId));
                    System.out.println("    Passenger: " + pasajeroId + " off the train");
                    if(passengers.size() ==0){
                        System.out.println();
                        canUnboard = false;
                        canBoard = true;
                        boardTrain.signalAll();                         
                    }else{
                        unboardTrain.signal();
                        System.out.print("      Remaining passengers: ");
                        for (int i = 0; i<passengers.size();i++){
                            System.out.print(" "+passengers.get(i));
                        }//for
                    }


                }else{
                    unboardTrain.signal();
                }//if
            }else{
                System.out.println();
                canUnboard = false;
                canBoard = true;
                boardTrain.signalAll();         
            }//if
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("Exception at unboarding");
        }finally{
            l.unlock();
        }//try


    }//fin bajar



    public int id() {
        return id;
    }//fin id


    public void run() {
        while(true){
            this.waitsFullTrain();
            try {
                this.goForRide();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }//fin while    

    }//fin run


}//fin clase