CSC409 Advanced Java Programming

Advanced Java ProgrammingUnit 112 min read

Advanced Java Concepts & Multithreading: Threads, Paths, and Anonymous Classes

Unit 1 of Advanced Java Programming covers core advanced concepts (path vs. classpath, anonymous inner classes) and deep dives into multithreading—lifecycle, synchronization, thread creation methods, and real-world applications like concurrent file I/O. Includes architecture diagrams, code examples, and exam-focused co

TAKEAWAYS:

  • Path vs. Classpath: Understand the distinction between system paths (executable locations) and classpath (JVM’s search path for .class files) to resolve ClassNotFoundException.
  • Anonymous Inner Classes: Use them for one-time implementations (e.g., Runnable for threads) to avoid verbose subclassing, but avoid overuse due to readability trade-offs.
  • Multithreading Basics: Master thread lifecycle (NEW → RUNNABLE → BLOCKED → TERMINATED) and creation methods (extends Thread, implements Runnable, ExecutorService).
  • Synchronization: Use synchronized blocks/methods to prevent race conditions in shared resources (e.g., file I/O, static variables).
  • Thread Priorities: Leverage setPriority() (1–10) for cooperative scheduling, though OS scheduling may override it.
  • Exam Pitfalls: Avoid mixing Thread and Runnable in the same program; prefer ExecutorService for modern thread pools.

1. Path vs. Classpath in Java

Definitions

  • Path: Environment variable listing directories where executable files (e.g., java, javac) reside.

    echo $PATH  # Linux/Mac
    echo %PATH% # Windows
    

    Example output: /usr/bin:/usr/local/bin:/opt/java/bin

  • Classpath: JVM’s search path for .class files or JARs. Set via -classpath or CLASSPATH environment variable.

    java -classpath ".:/lib/*" com.example.Main
    

Key Differences

Feature Path Classpath
Purpose Locates executables (binaries) Locates .class/JAR files
Default System-dependent (e.g., /usr/bin) . (current directory)
Syntax : (Unix) or ; (Windows) -classpath or CLASSPATH
Example Use Running java command Compiling (javac) or running classes

When to Use Which?

  • Use classpath when:
    • Compiling (javac) or running Java programs.
    • Resolving dependencies across packages (e.g., import com.example.*).
  • Use path when:
    • Configuring system-wide access to Java tools (javac, jar).

Example: Setting Classpath

// Compile with custom classpath
javac -classpath "lib/*:." MyProgram.java
// Run with classpath
java -classpath "lib/*:." MyProgram

Common Errors & Fixes

Error Cause Solution
ClassNotFoundException Classpath missing .class file Add directory to classpath: -cp ./bin
NoClassDefFoundError Class exists but not at runtime Rebuild project or check CLASSPATH
Could not find or load main Wrong classpath order Ensure . (current dir) is first

2. Anonymous Inner Classes

Definition

An anonymous inner class is a class declared without a name, typically for one-time use. It must extend a superclass or implement an interface.

Syntax

new SuperClassOrInterface() {
    // Override methods or add new ones
};

Use Cases

  1. Thread Creation:
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Thread running!");
        }
    });
    
  2. Event Handlers (e.g., Swing ActionListener):
    JButton button = new JButton("Click");
    button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Button clicked!");
        }
    });
    
  3. Comparators:
    List<String> list = new ArrayList<>();
    Collections.sort(list, new Comparator<String>() {
        public int compare(String s1, String s2) {
            return s1.length() - s2.length();
        }
    });
    

Advantages

  • Conciseness: Avoids verbose subclass declarations.
  • Flexibility: Can override methods or add fields/methods inline.
  • Local Scope: Accesses enclosing class variables (if non-final).

Disadvantages

  • Readability: Overuse can make code harder to debug.
  • Performance: Slight overhead due to anonymous class instantiation.
  • Limited Reusability: Not suitable for complex logic reused across methods.

When to Prefer Anonymous Classes?

Scenario Anonymous Class Traditional Class
One-time implementation (e.g., thread) ✅ Best ❌ Overkill
Complex logic reused in multiple places ❌ Avoid ✅ Better
Lambda expressions available (Java 8+) ❌ Replace with lambda ✅ Legacy support

Example: Anonymous Class vs. Lambda

// Anonymous class (Java 7)
Thread t1 = new Thread(new Runnable() {
    public void run() { System.out.println("Anonymous"); }
});

// Lambda (Java 8+)
Thread t2 = new Thread(() -> System.out.println("Lambda"));

3. Multithreading in Java

Definition

Multithreading allows concurrent execution of two or more parts of a program to maximize CPU utilization. Java supports threads via:

  1. Thread class (extends java.lang.Thread).
  2. Runnable interface (implements run()).
  3. ExecutorService (thread pools, Java 5+).

Why Multithreading?

  • Performance: Utilizes multi-core CPUs efficiently.
  • Responsiveness: Keeps GUI/apps responsive (e.g., background tasks).
  • Resource Sharing: Multiple threads can access shared data (with synchronization).

Thread Lifecycle

stateDiagram-v2
    [*] --> NEW: Thread created (new Thread())
    NEW --> RUNNABLE: start() called
    RUNNABLE --> RUNNING: OS scheduler picks thread
    RUNNING --> BLOCKED: wait(), sleep(), I/O
    RUNNING --> WAITING: wait(), join()
    RUNNING --> TIMED_WAITING: sleep(time), wait(time)
    BLOCKED --> RUNNABLE: Condition met (e.g., I/O complete)
    WAITING --> RUNNABLE: notify()/notifyAll()
    TIMED_WAITING --> RUNNABLE: Timeout reached
    RUNNING --> TERMINATED: run() completes
    TERMINATED --> [*]

Thread Creation Methods

Method Example Pros Cons
Extend Thread class class MyThread extends Thread { ... } Simple for single-thread tasks Single inheritance limitation
Implement Runnable new Thread(new MyRunnable()).start() Flexible (can extend another class) Extra boilerplate (Thread wrapper)
ExecutorService (Thread Pool) ExecutorService es = Executors.newFixedThreadPool(3); Reuses threads, manages lifecycle Complexity for simple tasks

Example: Thread Creation

// Method 1: Extend Thread
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }
}
MyThread t1 = new MyThread();
t1.start();

// Method 2: Implement Runnable
Runnable task = () -> System.out.println("Runnable thread: " + Thread.currentThread().getName());
Thread t2 = new Thread(task);
t2.start();

// Method 3: ExecutorService
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("Executor thread: " + Thread.currentThread().getName()));
executor.shutdown();

Thread Priorities

Java threads have priorities (1–10, where 1 = lowest, 10 = highest). Default is 5.

Thread t = new Thread(() -> System.out.println("High priority"));
t.setPriority(Thread.MAX_PRIORITY); // 10
t.start();
  • Note: Priority is hint-only; OS scheduler may ignore it.

Synchronization

Prevents race conditions when multiple threads access shared data.

Synchronized Methods
class Counter {
    private int count = 0;
    public synchronized void increment() { // Only one thread can execute this at a time
        count++;
    }
}
Synchronized Blocks
class Counter {
    private int count = 0;
    public void increment() {
        synchronized(this) { // Lock on 'this' object
            count++;
        }
    }
}
Deadlock Example
Object lock1 = new Object();
Object lock2 = new Object();

new Thread(() -> {
    synchronized(lock1) {
        synchronized(lock2) { System.out.println("Thread 1"); }
    }
}).start();

new Thread(() -> {
    synchronized(lock2) {
        synchronized(lock1) { System.out.println("Thread 2"); }
    }
}).start();
// Both threads wait indefinitely → Deadlock!

Inter-Thread Communication

Use wait(), notify(), and notifyAll() on shared objects.

class SharedData {
    private int data;
    private boolean ready = false;

    public synchronized void produce(int value) {
        while (ready) wait(); // Wait if consumer is ready
        data = value;
        ready = true;
        notify(); // Notify consumer
    }

    public synchronized int consume() {
        while (!ready) wait(); // Wait if producer hasn’t set data
        ready = false;
        notify(); // Notify producer
        return data;
    }
}

4. Practical Example: Concurrent File I/O

Task: Read employee data from keyboard and write to emp.doc using multithreading.

Solution

import java.io.*;
import java.util.Scanner;

class EmployeeWriter implements Runnable {
    private BufferedWriter writer;
    private String data;

    public EmployeeWriter(String filename, String data) throws IOException {
        this.writer = new BufferedWriter(new FileWriter(filename));
        this.data = data;
    }

    @Override
    public void run() {
        try {
            writer.write(data);
            writer.newLine();
            writer.close();
            System.out.println("Data written to file.");
        } catch (IOException e) {
            System.err.println("Error writing to file: " + e.getMessage());
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter employee name: ");
        String name = scanner.nextLine();

        // Thread for writing to file
        Thread writerThread = new Thread(new EmployeeWriter("emp.doc", name));
        writerThread.start();

        // Main thread continues
        System.out.println("Entering data...");
        scanner.close();
    }
}

Exception Handling

try {
    // Risky operations (e.g., file I/O, network)
} catch (FileNotFoundException e) {
    System.err.println("File not found!");
} catch (IOException e) {
    System.err.println("I/O error: " + e.getMessage());
} finally {
    if (writer != null) writer.close(); // Ensure resources are released
}

5. Exam Tip: Common Pitfalls & Focus Areas

Do’s

  • Thread Lifecycle: Draw the state diagram and explain transitions (e.g., NEW → RUNNABLE via start()).
  • Synchronization: Always use synchronized for shared resources. Mention wait()/notify() for inter-thread communication.
  • Anonymous Classes: Compare with lambdas (Java 8+) and traditional classes. Highlight use cases (e.g., Runnable for threads).
  • Classpath: Differentiate from PATH and explain -classpath vs. CLASSPATH environment variable.

Don’ts

  • Mix Thread and Runnable: Stick to one approach per program.
  • Ignore finally: Always close resources (e.g., FileWriter) in finally blocks.
  • Overuse Threads: Prefer ExecutorService for thread pools to avoid resource exhaustion.
  • Assume Priority Works: State that thread priority is a hint, not a guarantee.

High-Score Tips

  1. Code + Explanation: Always pair code snippets with step-by-step explanations (e.g., "Here, synchronized(this) ensures only one thread can increment count at a time").
  2. Diagrams: Draw the thread lifecycle diagram or classpath hierarchy.
  3. Error Handling: Show try-catch-finally blocks for file/network operations.
  4. Real-World Analogy: Relate multithreading to scenarios like:
    • A call center (threads = agents, ExecutorService = call queue).
    • Bank transactions (synchronization = locking accounts during transfers).

Past Exam Patterns

  • Short Questions (5 marks):
    • Differentiate path vs. classpath.
    • When to use anonymous inner classes (e.g., "for one-time Runnable implementations").
  • Programming (10–15 marks):
    • Write a multithreaded program (e.g., read from keyboard, write to file with synchronization).
    • Implement Runnable or Callable with ExecutorService.
  • Theory (10 marks):
    • Explain thread lifecycle with a diagram.
    • Describe synchronization and deadlocks with examples.

stateDiagram-v2
    [*] --> NEW: Thread created
    NEW --> RUNNABLE: start()
    RUNNABLE --> RUNNING: OS scheduler
    RUNNING --> BLOCKED: wait()/sleep()
    RUNNING --> WAITING: wait()
    RUNNING --> TIMED_WAITING: sleep(time)
    BLOCKED --> RUNNABLE: Condition met
    WAITING --> RUNNABLE: notify()
    TIMED_WAITING --> RUNNABLE: Timeout
    RUNNING --> TERMINATED: run() completes
    TERMINATED --> [*]

Based on the TU BSc CSIT syllabus for Advanced Java Programming (CSC409), unit 1.

Discussion

Loading…