Java Concurrency and Multithreading: A Comprehensive Study
Theoretical Foundations of Concurrency
Understanding Programs and Processes
A program represents static code that serves as the blueprint for application execution. In contrast, a process is the dynamic execution of a program, encompassing the entire lifecycle from code loading to completion. Operating systems allocate distinct memory spaces and system resources to each process, including code, data, and stack elements, ensuring complete independence of internal data and states across processes. In multitasking environments, process switching incurs significant CPU overhead.
Introduction to Multithreading
Multithreading creates multiple execution paths within a single process. Threads represent smaller units of execution compared to processes. Unlike processes, threads cannot exist independently and must reside within a process, sharing the process's memory space while maintaining their own lifecycle. Multithreading enables the illusion of simultaneous execution of multiple program statements. The creation, destruction, and switching of threads impose much lower overhead than processes, earning them the designation of "lightweight processes." Java supports two primary approaches to implementing multithreading: extending the Thread class and implementing the Runnable interface.
Thread Termination and Interruption
Threads terminate automatically when their run method completes or when an uncaught exception occurs within the run method, at which point they relinquish CPU control. The interrupt() method provides another mechanism for thread termination. This method sends an interrupt request to a thread and sets its "interrupted" status to true. If the target thread is in a blocked state, this action throws an InterruptedException.
Java offers several methods to check thread interruption status:
- static boolean interrupted() - Checks if the current thread has been interrupted and resets the "interrupted" status to false.
- boolean isInterrupted() - Checks if the current thread has been interrupted without modifying the "interrupted" status.
Thread States and Lifecycle
Java threads can exist in seven distinct states: New, Runnable, Running, Blocked, Waiting, Timed Waiting, and Terminated. Understanding these state transitions enables effective control over thread scheduling and CPU utilization, demonstrating the parallel nature of multithreading.
Daemon Threads
Daemon threads exist solely to provide services to other threads, such as timer threads. When only daemon threads remain active, the Java Virtual Machine (JVM) terminates. A thread can be converted to a daemon thread by calling setDaemon(true) before starting the thread.
Practical Implemantation: Thread Techniques
Objective and Requirements
This practical exercise aims to achieve the following learning outcomes:
- Understand fundamental thread concepts
- Master both thread creation techniques
- Comprehend thread priority attributes and scheduling mechanisms
- Grasp thread synchronization concepts and implementation techniques
Basic Thread Implementation
Let's examine a simple thread implementation that demonstrates both inheritance and interface approaches:
Implementation via Thread Inheritance
class LeftHandTask extends Thread {
public void run() {
for(int i = 0; i <= 5; i++) {
System.out.println("Student message!");
try {
sleep(500);
}
catch(InterruptedException e) {
System.out.println("Left hand task error.");
}
}
}
}
class RightHandTask extends Thread {
public void run() {
for(int i = 0; i <= 5; i++) {
System.out.println("Teacher message!");
try {
sleep(300);
}
catch(InterruptedException e) {
System.out.println("Right hand task error.");
}
}
}
}
public class ThreadDemo {
static LeftHandTask leftTask;
static RightHandTask rightTask;
public static void main(String[] args) {
leftTask = new LeftHandTask();
rightTask = new RightHandTask();
leftTask.start();
rightTask.start();
}
}
Implementation via Runnable Interface
Here's the same functionality implemented using the Runnable interface:
class LeftHandTask implements Runnable {
public void run() {
for(int i = 0; i <= 5; i++) {
System.out.println("Student message!");
try {
Thread.sleep(500);
}
catch(InterruptedException e) {
System.out.println("Left hand task error.");
}
}
}
}
class RightHandTask implements Runnable {
public void run() {
for(int i = 0; i <= 5; i++) {
System.out.println("Teacher message!");
try {
Thread.sleep(300);
}
catch(InterruptedException e) {
System.out.println("Right hand task error.");
}
}
}
}
public class ThreadDemo {
static Thread leftThread;
static Thread rightThread;
public static void main(String[] args) {
Runnable leftRunnable = new LeftHandTask();
Runnable rightRunnable = new RightHandTask();
leftThread = new Thread(leftRunnable);
rightThread = new Thread(rightRunnable);
leftThread.start();
rightThread.start();
}
}
Ball Animation Example
Let's explore a more complex example involving animated bouncing balls. This demonstrates thread usage for animation:
Ball Class
package animation;
import java.awt.geom.*;
public class Ball {
private static final int BALL_WIDTH = 15;
private static final int BALL_HEIGHT = 15;
private double x = 0;
private double y = 0;
private double deltaX = 1;
private double deltaY = 1;
public void move(Rectangle2D boundaries) {
x += deltaX;
y += deltaY;
if (x < boundaries.getMinX()) {
x = boundaries.getMinX();
deltaX = -deltaX;
}
if (x + BALL_WIDTH >= boundaries.getMaxX()) {
x = boundaries.getMaxX() - BALL_WIDTH;
deltaX = -deltaX;
}
if (y < boundaries.getMinY()) {
y = boundaries.getMinY();
deltaY = -deltaY;
}
if (y + BALL_HEIGHT >= boundaries.getMaxY()) {
y = boundaries.getMaxY() - BALL_HEIGHT;
deltaY = -deltaY;
}
}
public Ellipse2D getShape() {
return new Ellipse2D.Double(x, y, BALL_WIDTH, BALL_HEIGHT);
}
}
Ball Component
package animation;
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class BallCanvas extends JPanel {
private static final int DEFAULT_WIDTH = 450;
private static final int DEFAULT_HEIGHT = 350;
private java.util.List<ball> balls = new ArrayList<>();
public void add(Ball ball) {
balls.add(ball);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (Ball ball : balls) {
g2.fill(ball.getShape());
}
}
public Dimension getPreferredSize() {
return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
}</ball>
Animation Frame
package animation;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class BounceAnimation {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new AnimationFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
class AnimationFrame extends JFrame {
private BallCanvas canvas;
public static final int ANIMATION_STEPS = 1000;
public static final int DELAY = 3;
public AnimationFrame() {
setTitle("Bounce Animation");
canvas = new BallCanvas();
add(canvas, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel, "Start", event -> addBall());
addButton(buttonPanel, "Close", event -> System.exit(0));
add(buttonPanel, BorderLayout.SOUTH);
pack();
}
public void addButton(Container container, String title, ActionListener listener) {
JButton button = new JButton(title);
container.add(button);
button.addActionListener(listener);
}
public void addBall() {
try {
Ball ball = new Ball();
canvas.add(ball);
for (int i = 1; i <= ANIMATION_STEPS; i++) {
ball.move(canvas.getBounds());
canvas.paint(canvas.getGraphics());
Thread.sleep(DELAY);
}
}
catch (InterruptedException e) {
// Handle interruption
}
}
}
Thread-Based Animation Example
Here's an improved version of the ball animation that utilizes threads for smoother performance:
Thread-Based Ball Animation
package threadedAnimation;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ThreadedBounce {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new ThreadedAnimationFrame();
frame.setTitle("Threaded Bounce");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
class ThreadedAnimationFrame extends JFrame {
private BallCanvas canvas;
public static final int ANIMATION_STEPS = 1000;
public static final int DELAY = 5;
public ThreadedAnimationFrame() {
canvas = new BallCanvas();
add(canvas, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel, "Start", event -> addBall());
addButton(buttonPanel, "Close", event -> System.exit(0));
add(buttonPanel, BorderLayout.SOUTH);
pack();
}
public void addButton(Container container, String title, ActionListener listener) {
JButton button = new JButton(title);
container.add(button);
button.addActionListener(listener);
}
public void addBall() {
Ball ball = new Ball();
canvas.add(ball);
Runnable animationTask = () -> {
try {
for (int i = 1; i <= ANIMATION_STEPS; i++) {
ball.move(canvas.getBounds());
canvas.repaint();
Thread.sleep(DELAY);
}
}
catch (InterruptedException e) {
// Handle interruption
}
};
Thread animationThread = new Thread(animationTask);
animationThread.start();
}
}
Thread Priority Example
Demonstrating thread priority and scheduling:
class TaskRace extends Thread {
public static void main(String args[]) {
TaskRace[] runners = new TaskRace[4];
for(int i = 0; i < 4; i++) {
runners[i] = new TaskRace();
}
for(int i = 0; i < 4; i++) {
runners[i].start();
}
runners[1].setPriority(MIN_PRIORITY);
runners[3].setPriority(MAX_PRIORITY);
}
public void run() {
// Simulate some computation
for(int i = 0; i < 1000000; i++);
System.out.println(getName() + " thread with priority " + getPriority() + " has completed calculation!");
}
}
Bank Transaction Simulation
Simulating concurrent bank transactions to demonstrate thread synchronization issues:
Bank Class
package bankSimulation;
import java.util.*;
public class Bank {
private final double[] accounts;
public Bank(int numberOfAccounts, double initialBalance) {
accounts = new double[numberOfAccounts];
Arrays.fill(accounts, initialBalance);
}
public void transfer(int fromAccount, int toAccount, double amount) {
if (accounts[fromAccount] < amount) return;
System.out.print(Thread.currentThread());
accounts[fromAccount] -= amount;
System.out.printf(" Transfer amount: %10.2f from %d to %d", amount, fromAccount, toAccount);
accounts[toAccount] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
}
public double getTotalBalance() {
double sum = 0;
for (double balance : accounts) {
sum += balance;
}
return sum;
}
public int getNumberOfAccounts() {
return accounts.length;
}
}
Bank Test Class
package bankSimulation;
public class ConcurrentBankTest {
public static final int NUMBER_OF_ACCOUNTS = 100;
public static final double INITIAL_BALANCE = 1000;
public static final double MAX_TRANSFER_AMOUNT = 1000;
public static final int DELAY = 10;
public static void main(String[] args) {
Bank bank = new Bank(NUMBER_OF_ACCOUNTS, INITIAL_BALANCE);
for (int i = 0; i < NUMBER_OF_ACCOUNTS; i++) {
int fromAccount = i;
Runnable transactionTask = () -> {
try {
while (true) {
int toAccount = (int) (bank.getNumberOfAccounts() * Math.random());
double amount = MAX_TRANSFER_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
}
catch (InterruptedException e) {
// Handle interruption
}
};
Thread transactionThread = new Thread(transactionTask);
transactionThread.start();
}
}
}
Programming Exercise: User Informasion Collection
Creating a GUI application for collecting user information:
Main Application Class
package userInformationApp;
import java.awt.EventQueue;
import javax.swing.JFrame;
public class UserInformationApp {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
UserInformationFrame frame = new UserInformationFrame();
});
}
}
Frame Class
package userInformationApp;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
public class UserInformationFrame extends JFrame {
private JPanel namePanel;
private JPanel addressPanel;
private JPanel genderPanel;
private JPanel actionPanel;
private JTextField nameField;
private JComboBox qualificationComboBox;
private JTextField addressField;
private ButtonGroup genderButtonGroup;
private JRadioButton maleRadioButton;
private JRadioButton femaleRadioButton;
private JCheckBox singingCheckBox;
private JCheckBox dancingCheckBox;
private JCheckBox readingCheckBox;
public UserInformationFrame() {
this.setSize(750, 450);
this.setVisible(true);
this.setTitle("User Information");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
initializePanels();
setupLayout();
}
private void initializePanels() {
namePanel = new JPanel();
setNamePanel(namePanel);
addressPanel = new JPanel();
setAddressPanel(addressPanel);
genderPanel = new JPanel();
setGenderPanel(genderPanel);
actionPanel = new JPanel();
setActionPanel(actionPanel);
}
private void setNamePanel(JPanel panel) {
panel.setPreferredSize(new Dimension(700, 45));
panel.setLayout(new GridLayout(1, 4));
JLabel nameLabel = new JLabel("Name:");
nameField = new JTextField("");
JLabel qualificationLabel = new JLabel("Qualification:");
qualificationComboBox = new JComboBox();
qualificationComboBox.addItem("Graduate");
qualificationComboBox.addItem("Senior");
qualificationComboBox.addItem("Undergraduate");
panel.add(nameLabel);
panel.add(nameField);
panel.add(qualificationLabel);
panel.add(qualificationComboBox);
}
private void setAddressPanel(JPanel panel) {
panel.setPreferredSize(new Dimension(700, 50));
panel.setLayout(new GridLayout(1, 4));
JLabel addressLabel = new JLabel("Address:");
addressField = new JTextField();
JLabel hobbyLabel = new JLabel("Hobby:");
JPanel hobbyPanel = new JPanel();
hobbyPanel.setBorder(BorderFactory.createTitledBorder(""));
hobbyPanel.setLayout(new GridLayout(3, 1));
singingCheckBox = new JCheckBox("Singing");
dancingCheckBox = new JCheckBox("Dancing");
readingCheckBox = new JCheckBox("Reading");
hobbyPanel.add(singingCheckBox);
hobbyPanel.add(dancingCheckBox);
hobbyPanel.add(readingCheckBox);
panel.add(addressLabel);
panel.add(addressField);
panel.add(hobbyLabel);
panel.add(hobbyPanel);
}
private void setGenderPanel(JPanel panel) {
panel.setPreferredSize(new Dimension(700, 150));
panel.setLayout(new FlowLayout(FlowLayout.LEFT));
JLabel genderLabel = new JLabel("Gender:");
JPanel genderSelectionPanel = new JPanel();
genderSelectionPanel.setBorder(BorderFactory.createTitledBorder(""));
genderSelectionPanel.setLayout(new GridLayout(2, 1));
genderButtonGroup = new ButtonGroup();
maleRadioButton = new JRadioButton("Male");
femaleRadioButton = new JRadioButton("Female");
genderButtonGroup.add(maleRadioButton);
genderButtonGroup.add(femaleRadioButton);
genderSelectionPanel.add(maleRadioButton);
genderSelectionPanel.add(femaleRadioButton);
panel.add(genderLabel);
panel.add(genderSelectionPanel);
}
private void setActionPanel(JPanel panel) {
panel.setPreferredSize(new Dimension(700, 150));
panel.setLayout(new FlowLayout(FlowLayout.CENTER, 50, 10));
JButton submitButton = new JButton("Submit");
JButton resetButton = new JButton("Reset");
submitButton.addActionListener(e -> validateData());
resetButton.addActionListener(e -> resetForm());
panel.add(submitButton);
panel.add(resetButton);
}
private void setupLayout() {
FlowLayout mainLayout = new FlowLayout();
this.setLayout(mainLayout);
this.add(namePanel);
this.add(addressPanel);
this.add(genderPanel);
this.add(actionPanel);
}
private void validateData() {
String name = nameField.getText().trim();
String qualification = qualificationComboBox.getSelectedItem().toString().trim();
String address = addressField.getText().trim();
System.out.println("Name: " + name);
System.out.println("Qualification: " + qualification);
System.out.println("Address: " + address);
StringBuilder hobbies = new StringBuilder();
if (singingCheckBox.isSelected()) {
hobbies.append("Singing ");
}
if (dancingCheckBox.isSelected()) {
hobbies.append("Dancing ");
}
if (readingCheckBox.isSelected()) {
hobbies.append("Reading ");
}
System.out.println("Hobbies: " + hobbies.toString());
if (maleRadioButton.isSelected()) {
System.out.println("Gender: Male");
}
if (femaleRadioButton.isSelected()) {
System.out.println("Gender: Female");
}
}
private void resetForm() {
addressField.setText(null);
nameField.setText(null);
qualificationComboBox.setSelectedIndex(0);
singingCheckBox.setSelected(false);
dancingCheckBox.setSelected(false);
readingCheckBox.setSelected(false);
genderButtonGroup.clearSelection();
}
}
Thread Communication Exercise
Creating two threads that output messages in sequence:
package communicationExercise;
class MessageTask implements Runnable {
public void run(){
for(int i = 1; i <= 5; i++) {
System.out.println("Message from " + Thread.currentThread().getName() + ": " + i + " Hello");
try {
Thread.sleep(500);
}
catch(InterruptedException e) {
System.out.println("Message task error.");
}
}
}
}
public class ThreadCommunicationDemo {
static Thread leftThread;
static Thread rightThread;
public static void main(String[] args) {
Runnable leftTask = new MessageTask();
Runnable rightTask = new MessageTask();
leftThread = new Thread(leftTask, "LeftThread");
rightThread = new Thread(rightTask, "RightThread");
leftThread.start();
rightThread.start();
}
}
Summary
Through this comprehensive study of Java concurrency and multithreading, we have explored fundamental concepts including thread creation techniques, thread lifecycle management, priority scheduling, and synchronization mechanisms. The practical exercises demonstrated how to implement multithreaded applications, from simple thread implementations to complex animations and concurrent simulations. Understanding these concepts is essential for developing efficient, responsive applications that can leverage modern multi-core processors effectively.