📚 Chapters

☕ Java Programming

Unit 2 — IO Package and Multithreading

📂 Chapter 1 — IO Package
📂 IO PACKAGE - MIND MAP
Streams → Byte (InputStream/OutputStream) vs Character (Reader/Writer)
Serialisation → Object → byte stream (ObjectOutputStream) → De-serialisation reverses it (ObjectInputStream)
Filter Streams → Wrap a stream to add features (buffering, data types)
Pipe Streams → Direct thread-to-thread communication, no file needed
File Class → Manage file existence/creation/deletion; streams handle content

1. Introduction to IO Package & Streams

📖 What is a Stream?
A sequence of data flowing from source to destination for I/O operations. The java.io package provides all classes needed for input and output.

Two types: Input Stream (reading data) and Output Stream (writing data).
Stream Classification:
1. Byte Streams: Handle binary data (images, videos, any file). Read/write 8 bits (1 byte) at a time.
• Input: InputStream (abstract parent class)
• Output: OutputStream (abstract parent class)

2. Character Streams: Handle text data (only text files). Read/write 16 bits (Unicode characters) at a time.
• Input: Reader (abstract parent class)
• Output: Writer (abstract parent class)
Feature Byte Stream Character Stream
Data Type Binary (8-bit bytes) Text (16-bit Unicode)
Parent Classes InputStream, OutputStream Reader, Writer
Use For Images, videos, any file Text files only
Examples FileInputStream, FileOutputStream FileReader, FileWriter
💡 When to use which stream:
Byte Streams: Images (.jpg, .png), Videos (.mp4), Audio (.mp3), Binary files, Any non-text file
Character Streams: Text files (.txt), Java source (.java), HTML files (.html), CSV files

2. Input Streams

📖 Input Stream: Used to read data from a source (file, keyboard, network). InputStream handles bytes, Reader handles characters.
Reading Bytes — FileInputStream:
import java.io.*; // FileInputStream - Read bytes from file public class ByteStreamExample { public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("image.jpg"); int data; // Read byte by byte while ((data = fis.read()) != -1) { // Process byte data System.out.print(data + " "); } fis.close(); System.out.println("\nFile read successfully!"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
Reading Characters — FileReader:
import java.io.*; // FileReader - Read characters from file public class CharStreamExample { public static void main(String[] args) { try { FileReader fr = new FileReader("data.txt"); int ch; // Read character by character while ((ch = fr.read()) != -1) { System.out.print((char) ch); } fr.close(); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
💡 Key Point: read() returns -1 when the end of the stream is reached — that's how the loop knows to stop.

3. Output Streams

📖 Output Stream: Used to write data to a destination (file, console, network). OutputStream handles bytes, Writer handles characters.
Writing Bytes — FileOutputStream:
import java.io.*; // FileOutputStream - Write bytes to file public class ByteWriteExample { public static void main(String[] args) { try { FileOutputStream fos = new FileOutputStream("output.dat"); String data = "Hello Bytes!"; // Convert string to bytes and write fos.write(data.getBytes()); fos.close(); System.out.println("Data written successfully!"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
Writing Characters — FileWriter:
import java.io.*; // FileWriter - Write characters to file public class CharWriteExample { public static void main(String[] args) { try { FileWriter fw = new FileWriter("output.txt"); fw.write("Hello World!\n"); fw.write("Java File I/O is easy!"); fw.close(); System.out.println("File written successfully!"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
💡 Key Point: Always close output streams (or use try-with-resources) — unclosed streams can leave data un-flushed to disk.

4. Object Serialisation & De-serialisation

📖 Serialisation: The process of converting a Java object into a byte stream so it can be saved to a file, sent over a network, or stored in a database.

📖 De-serialisation: The reverse process — reconstructing a Java object back from that byte stream.
Requirements:
1. The class must implement the marker interface java.io.Serializable (it has no methods — it just tells the JVM "this class can be serialised").
2. Use ObjectOutputStream to serialise, ObjectInputStream to de-serialise.
Serialising an Object:
import java.io.*; class Student implements Serializable { String name; int rollNo; Student(String name, int rollNo) { this.name = name; this.rollNo = rollNo; } } public class SerializeDemo { public static void main(String[] args) throws IOException { Student s = new Student("Ankush", 101); FileOutputStream fos = new FileOutputStream("student.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s); // converts object -> byte stream -> file oos.close(); fos.close(); System.out.println("Object serialised successfully!"); } }
De-serialising an Object:
import java.io.*; public class DeserializeDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream("student.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Student s = (Student) ois.readObject(); // byte stream -> object System.out.println("Name: " + s.name); System.out.println("Roll No: " + s.rollNo); ois.close(); fis.close(); } } // Output: Name: Ankush Roll No: 101
💡 Key Points:
• Class must implement Serializable, or a NotSerializableException is thrown.
• Mark sensitive fields transient to exclude them from serialisation (e.g. transient String password;).
serialVersionUID is used to verify that the sender and receiver of a serialised object have compatible class versions.
• Used in: saving game state, caching, sending objects over a network (RMI), session storage.

5. Filter and Pipe Streams

Filter Streams:
📖 Filter Stream: A "wrapper" stream that sits on top of another stream and adds extra functionality — like buffering, filtering, or converting data — without changing the underlying source.

Examples: BufferedInputStream, BufferedOutputStream, DataInputStream, DataOutputStream.
import java.io.*; public class FilterStreamDemo { public static void main(String[] args) throws IOException { // DataOutputStream lets us write primitive types directly DataOutputStream dos = new DataOutputStream( new FileOutputStream("data.bin")); dos.writeInt(101); dos.writeDouble(85.5); dos.writeUTF("Ankush"); dos.close(); // DataInputStream reads them back in the SAME order DataInputStream dis = new DataInputStream( new FileInputStream("data.bin")); System.out.println(dis.readInt()); // 101 System.out.println(dis.readDouble()); // 85.5 System.out.println(dis.readUTF()); // Ankush dis.close(); } }
Pipe Streams:
📖 Pipe Stream: Connects the output of one thread directly to the input of another thread, without using a file. Used for communication between two threads running in the same JVM.

Classes: PipedInputStream & PipedOutputStream (bytes), PipedReader & PipedWriter (characters).
import java.io.*; public class PipeStreamDemo { public static void main(String[] args) throws IOException { PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos); // Writer thread Thread writer = new Thread(() -> { try { pos.write("Hello from writer thread!".getBytes()); pos.close(); } catch (IOException e) {} }); // Reader thread Thread reader = new Thread(() -> { try { int data; while ((data = pis.read()) != -1) { System.out.print((char) data); } } catch (IOException e) {} }); writer.start(); reader.start(); } } // Output: Hello from writer thread!
💡 Exam Tip:
• Filter streams = add functionality (buffering/data-type reading) to an existing stream — "decorator" pattern.
• Pipe streams = direct thread-to-thread communication, no file/disk involved.

6. IO Files

📖 The File Class: java.io.File represents a file or directory path on disk. It doesn't read/write file content itself — it's used to check, create, delete, and inspect files before you open a stream on them.
import java.io.*; public class FileClassDemo { public static void main(String[] args) throws IOException { File f = new File("notes.txt"); if (!f.exists()) { f.createNewFile(); // creates an empty file System.out.println("File created: " + f.getName()); } else { System.out.println("File already exists"); } System.out.println("Path: " + f.getAbsolutePath()); System.out.println("Size: " + f.length() + " bytes"); System.out.println("Can read: " + f.canRead()); System.out.println("Can write: " + f.canWrite()); // f.delete(); // deletes the file // f.mkdir(); // creates a directory } }
MethodPurpose
exists()Checks if file/directory exists
createNewFile()Creates a new empty file
delete()Deletes the file
mkdir()Creates a directory
listFiles()Lists contents of a directory
length()File size in bytes
💡 Key Point: Use File to manage the file itself (existence, permissions, deletion); use streams (FileReader/FileWriter/BufferedReader...) to read or write its contents.

7. Sample Programs on IO Files

📖 File I/O Operations:
Java provides classes to read from and write to files.

Key classes: FileReader, BufferedReader (reading), FileWriter, PrintWriter (writing).
Reading from File - Line by Line:
BufferedReader: Reads text efficiently by buffering characters. Provides readLine() for reading entire lines. Faster than FileReader alone.
import java.io.*; public class FileReadExample { public static void main(String[] args) { try { // Create FileReader FileReader fr = new FileReader("students.txt"); // Wrap in BufferedReader for efficiency BufferedReader br = new BufferedReader(fr); String line; int lineNumber = 1; // Read line by line while ((line = br.readLine()) != null) { System.out.println(lineNumber + ": " + line); lineNumber++; } // Close the reader br.close(); } catch (FileNotFoundException e) { System.out.println("File not found: " + e.getMessage()); } catch (IOException e) { System.out.println("Error reading file: " + e.getMessage()); } } } // If students.txt contains: // Raj // Priya // Amit Output: 1: Raj 2: Priya 3: Amit
Writing to File - PrintWriter:
PrintWriter: Writes formatted text to files. Provides println(), print(), printf() methods like System.out.
import java.io.*; public class FileWriteExample { public static void main(String[] args) { try { // Create FileWriter FileWriter fw = new FileWriter("output.txt"); // Wrap in PrintWriter for convenience PrintWriter pw = new PrintWriter(fw); // Write data pw.println("Student Records"); pw.println("================"); pw.println("Name: Raj Kumar"); pw.println("Roll No: 101"); pw.println("Marks: 85"); pw.println(); pw.println("Name: Priya Sharma"); pw.println("Roll No: 102"); pw.println("Marks: 92"); // Close the writer pw.close(); System.out.println("Data written to output.txt successfully!"); } catch (IOException e) { System.out.println("Error writing file: " + e.getMessage()); } } } Output (in output.txt file): Student Records ================ Name: Raj Kumar Roll No: 101 Marks: 85 Name: Priya Sharma Roll No: 102 Marks: 92
💡 File I/O Best Practices:
• Always close streams after use (or use try-with-resources)
• Handle FileNotFoundException separately from IOException
• Use BufferedReader for efficient line-by-line reading
• Use PrintWriter for formatted writing
• Append mode: new FileWriter("file.txt", true)
• Check if file exists: new File("name.txt").exists();
💡 Try-with-resources (Automatic Close):
import java.io.*; public class AutoCloseExample { public static void main(String[] args) { // Try-with-resources automatically closes streams try (BufferedReader br = new BufferedReader( new FileReader("data.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } // No need to call br.close() - automatic! } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
🧵 Chapter 2 — Multithreading
🧵 MULTITHREADING - MIND MAP
Thread → Lightweight subprocess; multiple threads share memory, run concurrently
Lifecycle → New → Runnable → Running → Blocked/Waiting → Terminated
Creating Threads → extend Thread class OR implement Runnable interface
Synchronisation → synchronized keyword prevents race conditions on shared data
Extras (not in syllabus) → Priorities, Inter-thread comm., suspend/resume/stop

1. Multithreading — Introduction, Advantages & Issues

📖 What is a Thread?
A lightweight subprocess — smallest unit of processing. Has its own call stack but shares memory with other threads.

Multithreading: Executing multiple threads simultaneously to maximize CPU utilization.
Process vs Thread:
Process Thread
Heavyweight Lightweight
Separate memory space Shares memory space
High creation cost Low creation cost
Isolated (no direct sharing) Can share data easily
Example: Multiple programs Example: Multiple tabs in browser
Advantages of Multithreading:
1. Better CPU Utilization: Other threads execute while one waits for I/O.

2. Improved Responsiveness: UI stays responsive during background tasks.

3. Resource Sharing: Threads share memory, reducing overhead.

4. Faster Execution: Parallel execution on multi-core processors.

5. Simplified Structure: Complex applications easier to design with threads.
Simple Thread Example:
class MyThread extends Thread { public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); } } } public class ThreadDemo { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); // Starts thread 1 t2.start(); // Starts thread 2 } } Output (may vary - concurrent execution): Thread-0: 1 Thread-1: 1 Thread-0: 2 Thread-1: 2 Thread-0: 3 Thread-1: 3 Thread-0: 4 Thread-1: 4 Thread-0: 5 Thread-1: 5
💡 Key Points:
• Each thread runs independently
• Output order not guaranteed (concurrent execution)
• Threads share same memory space
• Use start() method (NOT run() directly)
Issues / Disadvantages of Multithreading:
1. Race Condition: Multiple threads accessing/modifying shared data at the same time can produce inconsistent results.

2. Deadlock: Two or more threads wait forever for each other's locks — program hangs.

3. Increased Complexity: Debugging is harder — bugs may appear only occasionally (timing-dependent).

4. Context-Switching Overhead: CPU spends time switching between threads instead of doing useful work.

5. Synchronization Overhead: Locking shared resources to keep them safe can slow execution down.
💡 Race Condition Example: Two threads both read balance = 1000, both add 500 without waiting for the other — final balance should be 2000 but becomes 1500 because one update overwrites the other.

2. Thread Lifecycle

📖 Thread Life Cycle:
A thread goes through various states from creation to termination.

5 States: New, Runnable, Running, Blocked/Waiting, Terminated
💡 Analogy: Like attending an exam — New (registered), Runnable (waiting outside), Running (writing exam), Blocked (waiting for pen), Terminated (submitted).
Thread States in Detail:
1. New (Born) State:
Thread created but not yet started.
How: Thread t = new Thread();
2. Runnable State:
Thread ready to run, waiting for CPU time.
How: t.start();
3. Running State:
Thread executing its run() method - CPU allocated.
How: Thread scheduler selects from runnable pool.
4. Blocked/Waiting State:
Thread temporarily inactive, waiting for resource or I/O.
How: sleep(), wait(), I/O operations, waiting for lock
5. Terminated (Dead) State:
Thread finished execution or stopped. Cannot be restarted.
How: run() method completes, or stop() called
Thread Life Cycle Diagram: NEW | | start() ↓ RUNNABLE ←──────────────┐ | | | Scheduler picks | I/O complete, notify() ↓ | sleep() time up RUNNING | | | | sleep(), wait() | | I/O request | ↓ | BLOCKED/WAITING ─────────┘ RUNNING | | run() completes ↓ TERMINATED
Thread State Methods:
// Check thread state Thread t = new Thread(); System.out.println(t.getState()); // NEW t.start(); System.out.println(t.getState()); // RUNNABLE // Thread state constants: Thread.State.NEW Thread.State.RUNNABLE Thread.State.BLOCKED Thread.State.WAITING Thread.State.TIMED_WAITING Thread.State.TERMINATED
Complete Life Cycle Example:
class LifeCycleThread extends Thread { public void run() { System.out.println("State: RUNNING"); try { Thread.sleep(2000); // Goes to TIMED_WAITING System.out.println("Woke up from sleep"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("About to terminate"); } } public class ThreadLifeCycle { public static void main(String[] args) throws InterruptedException { LifeCycleThread t = new LifeCycleThread(); System.out.println("After creation: " + t.getState()); // NEW t.start(); System.out.println("After start(): " + t.getState()); // RUNNABLE Thread.sleep(100); System.out.println("While sleeping: " + t.getState()); // TIMED_WAITING t.join(); // Wait for thread to complete System.out.println("After completion: " + t.getState()); // TERMINATED } } Output: After creation: NEW After start(): RUNNABLE State: RUNNING While sleeping: TIMED_WAITING Woke up from sleep About to terminate After completion: TERMINATED

3. Simple Thread Programs — Creating Threads

📖 Two Ways to Create Threads:
1. Extend Thread class: Override run() method
2. Implement Runnable interface: Define run() method

Which to use? Runnable is preferred (allows extending other classes, better design).
Method 1: Extending Thread Class:
// Step 1: Create class extending Thread class MyThread extends Thread { // Step 2: Override run() method public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); try { Thread.sleep(500); // Pause 500ms } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadExample1 { public static void main(String[] args) { // Step 3: Create thread objects MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); // Step 4: Start threads t1.start(); t2.start(); } } Output: Thread-0: 1 Thread-1: 1 Thread-0: 2 Thread-1: 2 ...
Method 2: Implementing Runnable Interface:
// Step 1: Create class implementing Runnable class MyRunnable implements Runnable { // Step 2: Implement run() method public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class RunnableExample { public static void main(String[] args) { // Step 3: Create Runnable objects MyRunnable r = new MyRunnable(); // Step 4: Create Thread objects with Runnable Thread t1 = new Thread(r); Thread t2 = new Thread(r); // Step 5: Start threads t1.start(); t2.start(); } }
Feature Extending Thread Implementing Runnable
Inheritance Cannot extend other class Can extend other class
Code Simpler syntax Slightly more code
Object sharing Separate objects Same Runnable shared
Recommended ❌ Less flexible ✅ Preferred approach
Creating Multiple Threads:
class Counter implements Runnable { private String name; public Counter(String name) { this.name = name; } public void run() { for (int i = 1; i <= 3; i++) { System.out.println(name + ": Count " + i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class MultipleThreads { public static void main(String[] args) { // Create multiple threads Thread t1 = new Thread(new Counter("Thread-A")); Thread t2 = new Thread(new Counter("Thread-B")); Thread t3 = new Thread(new Counter("Thread-C")); // Start all threads t1.start(); t2.start(); t3.start(); } } Output (interleaved): Thread-A: Count 1 Thread-B: Count 1 Thread-C: Count 1 Thread-A: Count 2 Thread-B: Count 2 Thread-C: Count 2 Thread-A: Count 3 Thread-B: Count 3 Thread-C: Count 3
💡 Important Rules:
• Call start() to begin execution (NOT run())
• run() is called automatically by JVM
• Cannot call start() twice on same thread
• Each thread needs separate Thread object

4. Thread Synchronisation

📖 What is Synchronization?
Controls access to shared resources — only one thread can access synchronized code at a time.

Why? Prevents race conditions and data corruption.
Problem: Race Condition (Without Synchronization):
class Counter { private int count = 0; public void increment() { count++; // NOT thread-safe! } public int getCount() { return count; } } class CounterThread extends Thread { private Counter counter; public CounterThread(Counter counter) { this.counter = counter; } public void run() { for (int i = 0; i < 1000; i++) { counter.increment(); } } } public class RaceConditionDemo { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new CounterThread(counter); Thread t2 = new CounterThread(counter); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Expected: 2000"); System.out.println("Actual: " + counter.getCount()); // May be less! } } Output (unpredictable): Expected: 2000 Actual: 1847 // WRONG! Lost updates due to race condition
Solution: Synchronized Method:
class SynchronizedCounter { private int count = 0; // synchronized keyword - only one thread at a time public synchronized void increment() { count++; } public int getCount() { return count; } } class SyncThread extends Thread { private SynchronizedCounter counter; public SyncThread(SynchronizedCounter counter) { this.counter = counter; } public void run() { for (int i = 0; i < 1000; i++) { counter.increment(); } } } public class SynchronizationDemo { public static void main(String[] args) throws InterruptedException { SynchronizedCounter counter = new SynchronizedCounter(); Thread t1 = new SyncThread(counter); Thread t2 = new SyncThread(counter); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Expected: 2000"); System.out.println("Actual: " + counter.getCount()); // Always correct! } } Output: Expected: 2000 Actual: 2000 // CORRECT! synchronized ensures thread safety
Synchronized Block:
Synchronized Block: Synchronizes only a specific code section instead of the entire method.
class BankAccount { private int balance = 1000; public void withdraw(int amount) { System.out.println(Thread.currentThread().getName() + " attempting withdrawal"); // Only critical section is synchronized synchronized(this) { if (balance >= amount) { System.out.println(Thread.currentThread().getName() + " proceeding with withdrawal"); balance -= amount; System.out.println(Thread.currentThread().getName() + " completed. Balance: " + balance); } else { System.out.println(Thread.currentThread().getName() + " insufficient funds"); } } } } public class SyncBlockDemo { public static void main(String[] args) { BankAccount account = new BankAccount(); Thread t1 = new Thread(() -> account.withdraw(600), "Thread-1"); Thread t2 = new Thread(() -> account.withdraw(600), "Thread-2"); t1.start(); t2.start(); } } Output: Thread-1 attempting withdrawal Thread-2 attempting withdrawal Thread-1 proceeding with withdrawal Thread-1 completed. Balance: 400 Thread-2 insufficient funds
💡 Synchronization Best Practices:
• Use only when necessary (performance cost)
• Synchronize smallest code section possible
• Avoid nested synchronized blocks (deadlock risk)
• Static synchronized methods lock on class object
• Instance synchronized methods lock on 'this' object

5. Thread Priorities ⚠️ Not in Current Syllabus — Bonus

⚠️ Heads-up: This topic isn't listed in the current LMS syllabus for this unit. It's kept here as bonus/extra reading since it's a commonly useful Java concept — safe to skip if you're only prepping for the exam.
📖 Thread Priority:
Thread priority determines scheduling order. Higher priority threads get preference, but it's not guaranteed.

Priority Range: 1 (MIN) to 10 (MAX), Default is 5 (NORM)
💡 Analogy: Like a hospital ER — critical patients (priority 10) treated before minor injuries (priority 1).
Priority Constants:
Thread.MIN_PRIORITY = 1 // Lowest priority Thread.NORM_PRIORITY = 5 // Default priority Thread.MAX_PRIORITY = 10 // Highest priority
Setting Thread Priority:
class PriorityThread extends Thread { public void run() { System.out.println(Thread.currentThread().getName() + " - Priority: " + Thread.currentThread().getPriority()); } } public class PriorityDemo { public static void main(String[] args) { PriorityThread t1 = new PriorityThread(); PriorityThread t2 = new PriorityThread(); PriorityThread t3 = new PriorityThread(); // Set priorities t1.setPriority(Thread.MIN_PRIORITY); // 1 t2.setPriority(Thread.NORM_PRIORITY); // 5 t3.setPriority(Thread.MAX_PRIORITY); // 10 // Start threads t1.start(); t2.start(); t3.start(); } } Output: Thread-2 - Priority: 10 Thread-1 - Priority: 5 Thread-0 - Priority: 1
Complete Priority Example:
class Task implements Runnable { private String name; public Task(String name) { this.name = name; } public void run() { for (int i = 1; i <= 3; i++) { System.out.println(name + " (Priority: " + Thread.currentThread().getPriority() + ") - Count: " + i); } } } public class PriorityExample { public static void main(String[] args) { Thread highPriority = new Thread(new Task("HIGH"), "High-Priority"); Thread normalPriority = new Thread(new Task("NORM"), "Normal-Priority"); Thread lowPriority = new Thread(new Task("LOW"), "Low-Priority"); // Set priorities highPriority.setPriority(10); normalPriority.setPriority(5); lowPriority.setPriority(1); // Start all lowPriority.start(); normalPriority.start(); highPriority.start(); } } Output (high priority likely executes first): HIGH (Priority: 10) - Count: 1 HIGH (Priority: 10) - Count: 2 HIGH (Priority: 10) - Count: 3 NORM (Priority: 5) - Count: 1 NORM (Priority: 5) - Count: 2 NORM (Priority: 5) - Count: 3 LOW (Priority: 1) - Count: 1 LOW (Priority: 1) - Count: 2 LOW (Priority: 1) - Count: 3
💡 Important Notes:
• Priority is a hint to scheduler, not guaranteed
• Default priority inherited from parent thread
• Must be between 1-10 (throws IllegalArgumentException otherwise)
• Behavior varies across JVM implementations

6. Inter-Thread Communication ⚠️ Not in Current Syllabus — Bonus

⚠️ Heads-up: This topic isn't listed in the current LMS syllabus for this unit. It's kept here as bonus/extra reading since it's a commonly useful Java concept — safe to skip if you're only prepping for the exam.
📖 Inter Thread Communication:
Mechanism for threads to coordinate using wait(), notify(), and notifyAll() methods. Used in producer-consumer problems.

Three methods: wait() - release lock & wait, notify() - wake one thread, notifyAll() - wake all threads
💡 Analogy: Chef (producer) makes food, waiter (consumer) serves it. Waiter wait()s if no food; chef notify()s when ready.
wait(), notify(), notifyAll() Methods:
Method Purpose Must be called in
wait() Release lock, wait for notification synchronized block/method
notify() Wake up one waiting thread synchronized block/method
notifyAll() Wake up all waiting threads synchronized block/method
Producer-Consumer Problem:
class SharedQueue { private int data; private boolean available = false; // Producer produces data public synchronized void produce(int value) { while (available) { try { wait(); // Wait if data already available } catch (InterruptedException e) { e.printStackTrace(); } } data = value; available = true; System.out.println("Produced: " + value); notify(); // Notify consumer } // Consumer consumes data public synchronized int consume() { while (!available) { try { wait(); // Wait if no data available } catch (InterruptedException e) { e.printStackTrace(); } } available = false; System.out.println("Consumed: " + data); notify(); // Notify producer return data; } } class Producer extends Thread { private SharedQueue queue; public Producer(SharedQueue queue) { this.queue = queue; } public void run() { for (int i = 1; i <= 5; i++) { queue.produce(i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread { private SharedQueue queue; public Consumer(SharedQueue queue) { this.queue = queue; } public void run() { for (int i = 1; i <= 5; i++) { queue.consume(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ProducerConsumerDemo { public static void main(String[] args) { SharedQueue queue = new SharedQueue(); Producer producer = new Producer(queue); Consumer consumer = new Consumer(queue); producer.start(); consumer.start(); } } Output: Produced: 1 Consumed: 1 Produced: 2 Consumed: 2 Produced: 3 Consumed: 3 Produced: 4 Consumed: 4 Produced: 5 Consumed: 5
💡 Important Rules:
• wait(), notify(), notifyAll() are in Object class (not Thread)
• Must be called inside synchronized context
• wait() releases lock; notify() doesn't release lock immediately
• Use notifyAll() when multiple threads waiting
• Always check condition in while loop (spurious wakeups)

7. Suspending, Resuming & Stopping Threads ⚠️ Not in Current Syllabus — Bonus

⚠️ Heads-up: This topic isn't listed in the current LMS syllabus for this unit. It's kept here as bonus/extra reading since it's a commonly useful Java concept — safe to skip if you're only prepping for the exam.
📖 Thread Control Methods:
suspend(), resume(), stop() are DEPRECATED (cause deadlocks, inconsistent state).

Safe alternative: Use volatile boolean flags with interrupts.
Why Deprecated Methods are Dangerous:
suspend(): Holds locks → Deadlock risk
resume(): Can't resume if not suspended → Race condition
stop(): Immediately terminates → Inconsistent object state
Safe Alternative: Using Flags:
class ControlledThread extends Thread { private volatile boolean suspended = false; private volatile boolean stopped = false; public void run() { System.out.println("Thread started"); for (int i = 1; i <= 10 && !stopped; i++) { // Check if suspended synchronized(this) { while (suspended) { try { wait(); // Wait until resumed } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println("Count: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("Thread interrupted"); } } System.out.println("Thread stopped"); } // Safe suspend using flag public void suspendThread() { suspended = true; } // Safe resume using notify public synchronized void resumeThread() { suspended = false; notify(); } // Safe stop using flag public void stopThread() { stopped = true; } } public class ThreadControlDemo { public static void main(String[] args) throws InterruptedException { ControlledThread thread = new ControlledThread(); thread.start(); Thread.sleep(2000); System.out.println("\n--- SUSPENDING THREAD ---"); thread.suspendThread(); Thread.sleep(2000); System.out.println("--- RESUMING THREAD ---\n"); thread.resumeThread(); Thread.sleep(2000); System.out.println("\n--- STOPPING THREAD ---"); thread.stopThread(); } } Output: Thread started Count: 1 Count: 2 Count: 3 --- SUSPENDING THREAD --- --- RESUMING THREAD --- Count: 4 Count: 5 Count: 6 --- STOPPING THREAD --- Thread stopped
Using interrupt() for Stopping:
class InterruptibleThread extends Thread { public void run() { try { for (int i = 1; i <= 10; i++) { if (Thread.interrupted()) { System.out.println("Thread interrupted at count: " + i); return; // Exit gracefully } System.out.println("Count: " + i); Thread.sleep(500); } } catch (InterruptedException e) { System.out.println("Thread interrupted during sleep"); } } } public class InterruptDemo { public static void main(String[] args) throws InterruptedException { InterruptibleThread thread = new InterruptibleThread(); thread.start(); Thread.sleep(2000); System.out.println("\nInterrupting thread...\n"); thread.interrupt(); // Safe way to signal stop } } Output: Count: 1 Count: 2 Count: 3 Interrupting thread... Thread interrupted during sleep
Comparison Table:
Method Old (Deprecated) Safe Alternative
Suspend suspend() volatile boolean + wait()
Resume resume() notify()/notifyAll()
Stop stop() volatile boolean flag or interrupt()
💡 Best Practices:
• NEVER use suspend(), resume(), stop()
• Use volatile boolean flags for control
• Use interrupt() for cooperative cancellation
• Always handle InterruptedException properly
• Check flags/interrupts frequently in loops
🎯 Why volatile keyword:
• Ensures variable changes are immediately visible to all threads
• Prevents thread-local caching of variables
• Essential for boolean flags in multithreading
Quick Revision — Last Minute Exam Prep!
📌 How to Use: Read this 5-10 minutes before exam. Contains all important points in condensed form. Focus on tables, comparisons, and key topics!

💾 Serialisation & De-serialisation (5M)

Serialisation: Object → byte stream (save/send it)
De-serialisation: byte stream → Object (get it back)

Rule: Class must implement Serializable (marker interface, no methods)
// Serialize ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("s.ser")); oos.writeObject(obj); // Deserialize ObjectInputStream ois = new ObjectInputStream( new FileInputStream("s.ser")); Student s = (Student) ois.readObject();
Trick: transient keyword excludes a field from being serialised.

🔧 Filter & Pipe Streams (2M)

Filter Stream: Wraps another stream to add features — e.g. DataInputStream, BufferedInputStream

Pipe Stream: Connects two threads directly (no file) — PipedInputStream / PipedOutputStream

📁 File Class Methods (2M)

exists() | checks if file exists createNewFile() | creates empty file delete() | deletes file mkdir() | creates directory listFiles() | list directory contents length() | file size in bytes

🔄 Byte Stream vs Character Stream (IMP!)

Byte Stream | Character Stream ------------------------ | ----------------------- Binary data (8-bit) | Text data (16-bit Unicode) InputStream/OutputStream | Reader/Writer Images, videos, any file | Text files only FileInputStream | FileReader FileOutputStream | FileWriter No encoding | Unicode encoding
Trick: Byte = Binary (B-B), Character = Text (C-T)

📖 BufferedReader & PrintWriter (5M)

BufferedReader: Reads text efficiently by buffering characters
• Reads in chunks (not char by char) — faster
readLine() reads entire line at once

PrintWriter: Writes formatted text to file
println() writes line with newline
• Auto-flushes with constructor flag
// Read file BufferedReader br = new BufferedReader(new FileReader("data.txt")); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); // Write file PrintWriter pw = new PrintWriter(new FileWriter("out.txt")); pw.println("Hello World"); pw.close();

⚠️ Advantages & Issues of Multithreading (5M)

Advantages: Better CPU use, responsiveness, resource sharing, faster execution

Issues: Race conditions, Deadlock, hard to debug, context-switch overhead, sync overhead

🧵 Multithreading (2M)

Definition: Executing multiple threads simultaneously within a single program.

Advantages:
✅ Better CPU utilization (no idle time)
✅ Improved responsiveness
✅ Resource sharing between threads
✅ Faster execution of independent tasks

🔄 Thread Life Cycle (5M)

5 States:
1. New — Thread object created, not started
2. Runnable — start() called, waiting for CPU
3. Running — run() method executing
4. Blocked/Waiting — sleep(), wait(), I/O
5. Terminated — run() completed or exception
New → start() → Runnable → CPU assigned → Running Running → sleep()/wait() → Blocked/Waiting Running → run() done → Terminated Blocked → notify()/timeout → Runnable

🚀 Creating Threads — 2 Ways (IMP!)

Extend Thread class | Implement Runnable ------------------------ | ----------------------- class A extends Thread | class A implements Runnable Override run() | Override run() new A().start() | new Thread(new A()).start() Cannot extend other class| Can extend other class Less flexible | More flexible (preferred)
// Way 1: Extend Thread class MyThread extends Thread { public void run() { System.out.println("Thread running"); } } new MyThread().start(); // Way 2: Implement Runnable (Preferred) class MyTask implements Runnable { public void run() { System.out.println("Task running"); } } new Thread(new MyTask()).start();
Why Runnable preferred? Java doesn't support multiple inheritance with classes. Runnable allows extending another class too.

⚡ Thread Priorities (2M) ⚠️ Extra

Range: 1 to 10 (higher = more chance, not guaranteed)

MIN_PRIORITY = 1
NORM_PRIORITY = 5 (default)
MAX_PRIORITY = 10
t.setPriority(Thread.MAX_PRIORITY); // Set to 10 int p = t.getPriority(); // Get priority

🔒 Synchronization (5M)

Why needed? Multiple threads accessing shared resource → race condition → data corruption

Solution: synchronized keyword — only one thread at a time
// Synchronized Method synchronized void increment() { count++; } // Synchronized Block synchronized(obj) { // critical section - only one thread enters }

💬 Inter-Thread Communication (IMP!) ⚠️ Extra

wait() | notify() | notifyAll() ----------- | ---------------- | ---------------- Release lock | Wake one thread | Wake all threads Thread waits | Specific thread | All waiting threads Must be in synchronized block/method
Used in: Producer-Consumer problem. Producer produces → notify() → Consumer consumes → wait() for more.

🎮 Thread Control (2M) ⚠️ Extra

Deprecated: suspend(), resume(), stop() — deadlock risk!
Safe Way: Use volatile boolean flags
volatile boolean running = true;
while(running) { // work }
Stop: running = false;

⚠️ Common Exam Mistakes

❌ Not handling checked exceptions (IOException, SQLException)
❌ Empty catch blocks — bad practice
❌ Confusing throw vs throws
❌ Using deprecated methods (stop, suspend, resume)
❌ Forgetting to close streams (use finally/try-with-resources)
❌ Not calling start() — calling run() directly doesn't create thread
❌ Using wait/notify outside synchronized block
❌ Confusing Byte Stream with Character Stream

💻 Must Practice Programs

1. Create and use user-defined package
2. File read using BufferedReader
3. File write using PrintWriter
4. try-catch-finally with multiple catch
5. Custom exception creation and usage
6. throw vs throws demonstration
7. Create thread by extending Thread class
8. Create thread by implementing Runnable
9. Thread synchronization (synchronized counter)
10. Producer-Consumer using wait/notify

✅ Pre-Exam Checklist

☑ 6 built-in packages with examples
☑ User-defined package creation steps
☑ 4 ways to access packages
☑ Byte vs Character stream table
☑ BufferedReader & PrintWriter programs
☑ Exception hierarchy
☑ Checked vs Unchecked table
☑ 6 common exceptions
☑ try-catch-finally with multiple catch
☑ throw vs throws table
☑ Custom exception program
☑ Thread lifecycle (5 states)
☑ Thread creation (2 ways) with programs
☑ Priorities (1, 5, 10)
☑ Synchronization (method + block)
☑ wait/notify/notifyAll
☑ Volatile flags for safe thread control

🎯 Exam Strategy

2 Mark Questions:
• 2-3 sentences or 3-4 points
• Time: 3-4 minutes max
• Add example if time permits

5 Mark Questions:
• Definition + Explanation + Code + Output
• Time: 7-8 minutes
• Draw comparison tables when asked "differentiate"

Code Questions:
• Write proper syntax with import statements
• Add comments for explanation
• Show expected output
• Use meaningful variable names
🌟 All the Best!
Packages, Exceptions aur Multithreading — teeno chapters practice-based hain! Tables yaad karo (Byte vs Char, Checked vs Unchecked, throw vs throws), programs practice karo, aur exam ready ho! 💪☕
📄 Previous Year Questions

Previous Year Question Paper Not Available Yet

Previous year question papers for this unit are not available yet. If you have the question paper, please share it through the contact page so it can be added for other students.