Java concurrent: Airport Simulation- how to allow planes to access different gates concurrently

I’m new to Java thread. I’m designing about the airport simulation. I wanted to allow my plane to access the different gates concurrently. Eg: While plane 1 is in the gate 1, other planes are able to be access gate 2,3,4.

package assignment_self;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;

class Runway{
    
    static Semaphore capacity = new Semaphore(4);
    
    synchronized void required_to_land(Plane plane) throws InterruptedException
    {
        System.out.println("Plane "+ plane.id + " is requesting to land!");
        System.out.println("Available gates left: " + capacity.availablePermits());
        
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(Runway.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        access_runway(plane);
    }
    
    void access_runway(Plane plane) throws InterruptedException
    {
        if(capacity.availablePermits()>0)
        {
            capacity.acquire();
            System.out.println("Plane "+ plane.id+ " had accessed the runway!");
        
            try {
               Thread.sleep(new Random().nextInt(5)*1000);
            } catch (InterruptedException ex) {
               Logger.getLogger(Runway.class.getName()).log(Level.SEVERE, null, ex);
            }
            
            access_gate1(plane);
        }
        else
        {
            System.out.println("ATC: All gateways are occupy! Please wait for a moment Plane "+ plane.id);
        }
    }
    
    void access_gate1(Plane plane) throws InterruptedException
    {
        System.out.println("Plane " + plane.id+ " is landing at gate 1!");
        Thread.sleep(1000);
        System.out.println("Plane " + plane.id+ " is letting passengers down!");
        Thread.sleep(1000);
        System.out.println("Plane "+ plane.id+ " is leaving gate 1!");
        capacity.release();
    }
    
    void access_gate2(Plane plane) throws InterruptedException
    {
        System.out.println("Plane "+ plane.id+ " is landing at gate 2!");
        Thread.sleep(1000);
        System.out.println("Plane " + plane.id+ " is letting passengers down!");
        Thread.sleep(1000);
        System.out.println("Plane "+ plane.id+ " is leaving gate 2!");
        capacity.release();
    }
    
    void access_gate3(Plane plane) throws InterruptedException
    {
        System.out.println("Plane "+ plane.id+ " is landing at gate 3!");
        Thread.sleep(1000);
        System.out.println("Plane " + plane.id+ " is letting passengers down!");
        Thread.sleep(1000);
        System.out.println("Plane "+ plane.id+ " is leaving gate 3!");
        capacity.release();
    }
    
    void access_gate4(Plane plane) throws InterruptedException
    {
        System.out.println("Plane "+ plane.id+ " is landing at gate 4!");
        Thread.sleep(1000);
        System.out.println("Plane " + plane.id+ " is letting passengers down!");
        Thread.sleep(1000);
        System.out.println("Plane "+ plane.id+ " is leaving gate 4!");
        capacity.release();
    }
}

class Plane extends Thread{
    Runway runway;
    int id;
    
    Plane(int id, Runway runway)
    {
        this.id = id;
        this.runway = runway;
    }
    
    @Override
    public void run()
    {
        try {
            runway.required_to_land(this);
        } catch (InterruptedException ex) {
            Logger.getLogger(Plane.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

public class Assignment_self {

    public static void main(String[] args) {
        // TODO code application logic here
        Runway runway= new Runway();
        
        for(int i=1 ; i<= 10 ; i++)
        {
            Plane p = new Plane(i, runway);
            p.start();
        }
    }
}

This is my current output, It runs endlessly, I knows it wrong.

Plane 2 is requesting to land!
Available gates left: 4
Plane 2 had accessed the runway!
Plane 2 is landing at gate 1!
Plane 2 is letting passengers down!
Plane 2 is leaving gate 1!
Plane 10 is requesting to land!
Available gates left: 4
Plane 10 had accessed the runway!
Plane 10 is landing at gate 1!
Plane 10 is letting passengers down!
Plane 10 is leaving gate 1!
Plane 9 is requesting to land!
Available gates left: 4

My desired output would be something like this. I just type it manually so is not 100% correct, but that’s the concept output I wish to have.

Plane 2 is requesting to land!
Available gates left: 4
Plane 2 had accessed the runway!
Plane 2 is landing at gate 1!
Plane 2 is letting passengers down!
Plane 10 is requesting to land!
Available gates left: 3
Plane 10 had accessed the runway!
Plane 10 is landing at gate 2!
Plane 2 is leaving gate 1!
Plane 10 is letting passengers down!
Plane 9 is requesting to land!
Available gates left: 3

I would really appreciate for you guys help as I have been stuck at here for long time. Thank you in advance!

Answer

The easiest way to have a concept of “available gates” is to have some sort of collection of available gates; and then “claim” and “release” gates from/to this collection.

For example, you could do something like:

// A field.
final Deque<Integer> availableGates = new ArrayDeque<>(Arrays.asList(1, 2, 3, 4));

// A method.
OptionalInt claimGate() {
  synchronized (availableGates) {
    if (availableGates.isEmpty()) {
      // You need to handle the possibility of no gates.
      return OptionalInt.empty();
    }

    // Pick a gate from the queue, however you like.
    return OptionalInt.of(availableGates.removeLast());
  }
}

// Another method.
void releaseGate(int gateNumber) {
  // Perhaps you also want to check that this gate number was previously claimed...
  synchronized (availableGates) {
    availableGates.add(gateNumber);
  }
}

Now, in access_runway:

void access_runway(Plane plane) {
  OptionalInt maybeGateNumber = claimGate();
  if (!maybeGateNumber.isPresent()) {
    System.out.println("ATC: All gateways are occupy! ...");
    return;
  }

  int gateNumber = maybeGateNumber.get();
  access_gate(plane, gateNumber);
}

and access_gate takes the gate number:

void access_gate(Plane plane, int gateNumber) {
  System.out.println("Plane " + plane.id+ " is landing at gate " + gateNumber + "!");
  // ...
  releaseGate(gateNumber);
}