Project Based Learning in Java (24CSH-301 / 24ITH-301) - Lab 4

Experiment 2.1: Data Structures, Collections, and Multithreading

Develop Java programs using dynamic data structures, the Collection interface, and synchronized multithreading to manage and manipulate data safely.

AimBuild Java applications with ArrayList, Collection-based searching, and synchronized thread coordination.
ObjectivesUse collections for flexible storage, traverse objects safely, and protect shared data during concurrent access.
Mapped COCO2, CO3

Lab Brief

What You Need To Build

Easy Level

Employee ArrayList System

Create a Java program that stores employee records in an ArrayList and supports add, update, remove, search, and display operations.

  • Use an Employee class for each record.
  • Store all records in List<Employee>.
  • Find employees by unique id.
  • Drive the program through a menu.
Medium Level

Card Collection Lookup

Store card objects using the Collection interface and display all cards that match a symbol entered by the user.

  • Represent each card with symbol and value.
  • Declare variables with Collection<Card>.
  • Traverse using enhanced for.
  • Handle the no-match case clearly.
Hard Level

Synchronized Ticket Booking

Simulate multiple passengers booking tickets at the same time using threads, priorities, and synchronized access to the shared ticket counter.

  • Create booking threads by extending Thread.
  • Protect shared tickets with a synchronized method.
  • Use thread priorities as scheduling hints.
  • Use join() before printing the final line.
Input/Apparatus used: Hardware - minimum 384 MB RAM, 100 GB hard disk. Software - JDK with any Java IDE such as Eclipse, NetBeans, or IntelliJ IDEA, or a plain terminal with javac and java.

Before Coding

Reading Material

Collections Framework

The Java Collections Framework provides interfaces such as Collection and List, and implementations such as ArrayList. Program to the interface when the algorithm only needs general operations such as add, remove, and iteration.

Collection<String> names = new ArrayList<>();
names.add("Aman");
names.add("Meera");

ArrayList

ArrayList is a resizable-array implementation of List. It keeps insertion order, allows indexed access, and is suitable for employee records when the program needs frequent searching and display.

List<Employee> employees = new ArrayList<>();
employees.add(new Employee(101, "Anaya", "HR", 45000));

Threads

A thread is one path of execution inside a Java program. Calling start() creates a new path and the JVM calls run() on that path. Calling run() directly does not start a new thread.

Thread worker = new Thread(task, "booking-worker");
worker.start();
worker.join();

Synchronization

When several threads update the same object, the update must be protected. A synchronized method lets only one thread execute that method on the same object at a time, preventing overselling in a ticket counter.

public synchronized void bookTickets(int count) {
    // check and update shared tickets here
}

Core Ideas

Prerequisite Concepts

Interface Reference, Class Object

The variable type can be an interface while the actual object is a class. This keeps code flexible because another implementation can be substituted later.

List<String> topics = new ArrayList<>();
Collection<Card> cards = new ArrayList<>();

Enhanced For Loop

The enhanced for loop is the simplest safe traversal pattern when every element must be inspected and no index is needed.

for (Card card : cards) {
    if (card.getSymbol().equalsIgnoreCase(symbol)) {
        card.display();
    }
}

Thread Priority

Priority is a scheduling hint, not a correctness guarantee. A high-priority thread may run earlier, but code must still be correct if the JVM schedules threads in another order.

vip.setPriority(Thread.MAX_PRIORITY);
regular.setPriority(Thread.NORM_PRIORITY);

Race Condition

A race condition occurs when the result depends on timing between threads. Ticket booking avoids it by checking and subtracting tickets inside one synchronized method.

if (requestedTickets <= availableTickets) {
    availableTickets -= requestedTickets;
}

Easy Level

Employee Records with ArrayList

Problem statement: Create a Java program to maintain employee records using an ArrayList. The program should allow the user to add, update, remove, search, and display employees.

Method Design

MethodPurpose
findEmployee(...)Searches the list by employee id and returns the matching object or null.
addEmployee(...)Reads details, checks duplicate id, and adds a new record.
updateEmployee(...)Finds the employee and replaces name, department, and salary.
removeEmployee(...)Removes the found object from the list.
displayEmployees(...)Prints all employee records in table form.
Employee ArrayList menu flow Start Create ArrayList of EmployeeShow menu repeatedly Choice?add/update/remove/search/display 0End Perform selected list operationReturn to menu until user exits
Figure 1. The menu repeats until the user chooses exit.

Algorithm

  1. Create an Employee class with id, name, department, salary, update, and display methods.
  2. Create an ArrayList to store employee objects.
  3. Display a menu for add, update, remove, search, display, and exit.
  4. For add, read details and reject duplicate employee ids.
  5. For update, remove, and search, first locate the employee by id.
  6. Repeat the menu until the user enters 0.

Java Program

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

class Employee {
    private int id;
    private String name;
    private String department;
    private double salary;

    public Employee(int id, String name, String department, double salary) {
        this.id = id;
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    public int getId() {
        return id;
    }

    public void update(String name, String department, double salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    public void display() {
        System.out.printf("%-6d %-15s %-12s %.2f%n", id, name, department, salary);
    }
}

public class EmployeeArrayListSystem {
    private static Employee findEmployee(List<Employee> employees, int id) {
        for (Employee employee : employees) {
            if (employee.getId() == id) {
                return employee;
            }
        }
        return null;
    }

    private static void addEmployee(List<Employee> employees, Scanner scanner) {
        System.out.print("Enter id: ");
        int id = Integer.parseInt(scanner.nextLine().trim());

        if (findEmployee(employees, id) != null) {
            System.out.println("Employee id already exists.");
            return;
        }

        System.out.print("Enter name: ");
        String name = scanner.nextLine().trim();

        System.out.print("Enter department: ");
        String department = scanner.nextLine().trim();

        System.out.print("Enter salary: ");
        double salary = Double.parseDouble(scanner.nextLine().trim());

        employees.add(new Employee(id, name, department, salary));
        System.out.println("Employee added.");
    }

    private static void updateEmployee(List<Employee> employees, Scanner scanner) {
        System.out.print("Enter id to update: ");
        int id = Integer.parseInt(scanner.nextLine().trim());
        Employee employee = findEmployee(employees, id);

        if (employee == null) {
            System.out.println("Employee not found.");
            return;
        }

        System.out.print("Enter new name: ");
        String name = scanner.nextLine().trim();

        System.out.print("Enter new department: ");
        String department = scanner.nextLine().trim();

        System.out.print("Enter new salary: ");
        double salary = Double.parseDouble(scanner.nextLine().trim());

        employee.update(name, department, salary);
        System.out.println("Employee updated.");
    }

    private static void removeEmployee(List<Employee> employees, Scanner scanner) {
        System.out.print("Enter id to remove: ");
        int id = Integer.parseInt(scanner.nextLine().trim());
        Employee employee = findEmployee(employees, id);

        if (employee == null) {
            System.out.println("Employee not found.");
            return;
        }

        employees.remove(employee);
        System.out.println("Employee removed.");
    }

    private static void searchEmployee(List<Employee> employees, Scanner scanner) {
        System.out.print("Enter id to search: ");
        int id = Integer.parseInt(scanner.nextLine().trim());
        Employee employee = findEmployee(employees, id);

        if (employee == null) {
            System.out.println("Employee not found.");
        } else {
            System.out.println("ID     Name            Department   Salary");
            employee.display();
        }
    }

    private static void displayEmployees(List<Employee> employees) {
        if (employees.isEmpty()) {
            System.out.println("No employee records available.");
            return;
        }

        System.out.println("ID     Name            Department   Salary");
        for (Employee employee : employees) {
            employee.display();
        }
    }

    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        Scanner scanner = new Scanner(System.in);

        try {
            int choice;
            do {
                System.out.println("\n1. Add  2. Update  3. Remove  4. Search  5. Display  0. Exit");
                System.out.print("Enter choice: ");
                choice = Integer.parseInt(scanner.nextLine().trim());

                switch (choice) {
                    case 1 -> addEmployee(employees, scanner);
                    case 2 -> updateEmployee(employees, scanner);
                    case 3 -> removeEmployee(employees, scanner);
                    case 4 -> searchEmployee(employees, scanner);
                    case 5 -> displayEmployees(employees);
                    case 0 -> System.out.println("Program ended.");
                    default -> System.out.println("Invalid choice.");
                }
            } while (choice != 0);
        } finally {
            scanner.close();
        }
    }
}

Sample Output

1. Add  2. Update  3. Remove  4. Search  5. Display  0. Exit
Enter choice: 1
Enter id: 101
Enter name: Anaya
Enter department: HR
Enter salary: 45000
Employee added.

1. Add  2. Update  3. Remove  4. Search  5. Display  0. Exit
Enter choice: 4
Enter id to search: 101
ID     Name            Department   Salary
101    Anaya           HR           45000.00

Medium Level

Card Lookup with Collection Interface

Problem statement: Create a Java program that stores cards in a Collection and displays all cards matching a symbol entered by the user.

Method Design

MethodPurpose
Card(...)Stores one card's symbol and value.
getSymbol()Returns the symbol used for lookup.
display()Prints one matching card.
findCardsBySymbol(...)Traverses the collection and returns all cards with the requested symbol.
Card symbol lookup flow Start Read cards into Collection<Card>symbol and value Read search symbol Card symbol matches?equalsIgnoreCase YesDisplay No more
Figure 2. Every card is inspected once during lookup.

Algorithm

  1. Create a Card class with symbol and value.
  2. Declare Collection<Card> cards = new ArrayList<>().
  3. Read the number of cards and add every card to the collection.
  4. Read the symbol to be searched.
  5. Traverse the collection and copy matching cards to another collection.
  6. Display matching cards or print a no-match message.

Java Program

import java.util.ArrayList;
import java.util.Collection;
import java.util.Scanner;

class Card {
    private final String symbol;
    private final String value;

    public Card(String symbol, String value) {
        this.symbol = symbol;
        this.value = value;
    }

    public String getSymbol() {
        return symbol;
    }

    public void display() {
        System.out.println(symbol + " - " + value);
    }
}

public class CardCollectionLookup {
    private static Collection<Card> findCardsBySymbol(Collection<Card> cards, String symbol) {
        Collection<Card> matchingCards = new ArrayList<>();

        for (Card card : cards) {
            if (card.getSymbol().equalsIgnoreCase(symbol)) {
                matchingCards.add(card);
            }
        }

        return matchingCards;
    }

    public static void main(String[] args) {
        Collection<Card> cards = new ArrayList<>();
        Scanner scanner = new Scanner(System.in);

        try {
            System.out.print("Enter number of cards: ");
            int count = Integer.parseInt(scanner.nextLine().trim());

            for (int i = 1; i <= count; i++) {
                System.out.println("Card " + i);
                System.out.print("Enter symbol: ");
                String symbol = scanner.nextLine().trim();

                System.out.print("Enter value: ");
                String value = scanner.nextLine().trim();

                cards.add(new Card(symbol, value));
            }

            System.out.print("Enter symbol to search: ");
            String requiredSymbol = scanner.nextLine().trim();

            Collection<Card> result = findCardsBySymbol(cards, requiredSymbol);

            if (result.isEmpty()) {
                System.out.println("No cards found for symbol " + requiredSymbol + ".");
            } else {
                System.out.println("Cards with symbol " + requiredSymbol + ":");
                for (Card card : result) {
                    card.display();
                }
            }
        } finally {
            scanner.close();
        }
    }
}

Sample Output

Enter number of cards: 4
Card 1
Enter symbol: Heart
Enter value: Ace
Card 2
Enter symbol: Spade
Enter value: King
Card 3
Enter symbol: Heart
Enter value: 10
Card 4
Enter symbol: Diamond
Enter value: Queen
Enter symbol to search: Heart
Cards with symbol Heart:
Heart - Ace
Heart - 10

Hard Level

Ticket Booking with Synchronized Threads

Problem statement: Create a ticket booking program where multiple threads try to book tickets from one shared counter. Use synchronization so tickets are not oversold, and assign priorities to booking threads.

Class and Method Design

Class / MethodPurpose
TicketCounterStores the shared number of available tickets.
bookTickets(...)Synchronized method that checks and updates tickets atomically.
BookingThreadRepresents one passenger's booking request.
run()Calls the counter's booking method when the thread starts.
join()Makes main wait until all booking threads finish.
Priority reminder: priority can influence scheduling, but it does not guarantee exact execution order. Synchronization is what guarantees correct ticket count.
Synchronized ticket booking flow Start Create shared TicketCounteravailableTickets = 5 Start booking threadsMAX, NORM, and MIN priority One thread enters synchronized methodother threads wait for the lock Enough tickets?requested <= available YesBook tickets NoReject request
Figure 3. Only one thread can check and update the shared ticket count at a time.

Algorithm

  1. Create a TicketCounter class with availableTickets.
  2. Write synchronized bookTickets to check availability and subtract tickets in one protected block.
  3. Create BookingThread by extending Thread.
  4. Store passenger name and requested tickets in each thread object.
  5. Set thread names and priorities through the constructor.
  6. Start all booking threads and call join() so the main thread waits for completion.

Java Program

class TicketCounter {
    private int availableTickets;

    public TicketCounter(int availableTickets) {
        this.availableTickets = availableTickets;
    }

    public synchronized void bookTickets(String passengerName, int requestedTickets) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " processing request for " + passengerName);

        if (requestedTickets <= availableTickets) {
            availableTickets -= requestedTickets;
            System.out.println(passengerName + " booked " + requestedTickets
                    + " ticket(s). Remaining tickets: " + availableTickets);
        } else {
            System.out.println(passengerName + " could not book " + requestedTickets
                    + " ticket(s). Only " + availableTickets + " left.");
        }
    }
}

class BookingThread extends Thread {
    private final TicketCounter counter;
    private final String passengerName;
    private final int requestedTickets;

    public BookingThread(TicketCounter counter, String passengerName,
                         int requestedTickets, int priority) {
        this.counter = counter;
        this.passengerName = passengerName;
        this.requestedTickets = requestedTickets;
        setName(passengerName + "-thread");
        setPriority(priority);
    }

    @Override
    public void run() {
        counter.bookTickets(passengerName, requestedTickets);
    }
}

public class TicketBookingSystem {
    public static void main(String[] args) throws InterruptedException {
        TicketCounter counter = new TicketCounter(5);

        BookingThread vip = new BookingThread(counter, "Ananya", 2, Thread.MAX_PRIORITY);
        BookingThread regular = new BookingThread(counter, "Ravi", 3, Thread.NORM_PRIORITY);
        BookingThread waiting = new BookingThread(counter, "Meera", 2, Thread.MIN_PRIORITY);

        vip.start();
        regular.start();
        waiting.start();

        vip.join();
        regular.join();
        waiting.join();

        System.out.println("All booking requests processed.");
    }
}

Sample Output

The exact order can vary because thread scheduling is controlled by the JVM and operating system.

Ananya-thread processing request for Ananya
Ananya booked 2 ticket(s). Remaining tickets: 3
Ravi-thread processing request for Ravi
Ravi booked 3 ticket(s). Remaining tickets: 0
Meera-thread processing request for Meera
Meera could not book 2 ticket(s). Only 0 left.
All booking requests processed.

Review

Quiz and Viva Questions

1. Why is ArrayList preferred over an array in the employee program?

ArrayList can grow and shrink dynamically, so employee records can be added and removed without creating a new array manually.

2. What is the benefit of declaring List<Employee> instead of ArrayList<Employee>?

It programs to the interface. The code depends on list behaviour, not the specific implementation, so the implementation can be changed later more easily.

3. What is the difference between Collection and Collections?

Collection is an interface for groups of elements. Collections is a utility class with static helper methods such as sorting and searching.

4. Why does the card program use an enhanced for loop?

The program needs to inspect every card and does not need an index, so enhanced for gives a clean traversal over any Collection.

5. What happens if no card has the requested symbol?

The result collection remains empty and the program prints a no-match message instead of failing silently.

6. What is a thread?

A thread is a path of execution inside a process. Java can run multiple threads so different tasks make progress concurrently.

7. What is the difference between start() and run()?

start() creates a new thread and then invokes run() on that thread. Calling run() directly executes like a normal method on the current thread.

8. Why is bookTickets synchronized?

It protects the shared ticket count. Only one thread can check and update the count at a time, so tickets are not oversold.

9. Does thread priority guarantee execution order?

No. Priority is only a scheduling hint. Correctness must come from synchronization and coordination, not from assuming a fixed order.

10. Why does the hard program use join()?

join() makes the main thread wait until each booking thread finishes before printing the final completion message.

Before Submission

Lab Submission Checklist