0% found this document useful (0 votes)
7 views46 pages

OOPS

The document discusses multithreading in Java, explaining key concepts such as the Thread class, Runnable interface, and the thread life cycle. It highlights the advantages of multithreading, including efficient CPU utilization and the ability to perform multiple tasks simultaneously. Additionally, it covers thread creation methods, thread states, and thread management techniques such as join() and isAlive().

Uploaded by

sandhya.check
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views46 pages

OOPS

The document discusses multithreading in Java, explaining key concepts such as the Thread class, Runnable interface, and the thread life cycle. It highlights the advantages of multithreading, including efficient CPU utilization and the ability to perform multiple tasks simultaneously. Additionally, it covers thread creation methods, thread states, and thread management techniques such as join() and isAlive().

Uploaded by

sandhya.check
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 46

EASWARI ENGINEERING COLLEGE

(Autonomous)

Unit -5
Thread, Generics and JDBC

Multithreading – Thread Class and Runnable Interface – Thread Life Cycle – Synchronization
– Introduction to Generics in Java, Java Database Connectivity

Multithreading is a Java feature that allows the concurrent execution of two or more parts of a

program (called threads) for maximum CPU utilization. Each thread runs independently but shares

the same memory space.

Key Concepts

 Thread: A thread is the smallest unit of execution in a program.


 Multitasking: Executing multiple tasks at the same time.
o Process-based multitasking: Each task is a separate process.
o Thread-based multitasking: Each task is a separate thread within the same process.
 Main Thread: Every Java application has one main thread by default.

Difference between Multiprocessing and Multithreading

There are two distinct types of multitasking: Process-based and Thread-based. It is


important to understand the difference between two. The Program in execution is defined as
Process. Thus, the process based multi-tasking is the feature that allows your computer to run
two or more programs concurrently. For example, we are able to use the java compiler and text
editor at the same time. Another example is, we are able to hear the music and also able to get
the print outs from the printer.
In the thread-based multitasking environment, the thread is the smallest unit of
dispatchable code. This means that the single program can contain two or more parts, each part
of the program is called, Thread. For example, the text editor can be formatting the text and also
printing the text. Although the Java programs make use of the process-based multi-tasking
environments, but the process-based multi-tasking is not under the control of java, Whereas the
thread-based multitasking is under the control of Java.
Process-Based Multitasking Thread-Based Multitasking
This deals with "Big Picture" This deals with Details
These are Heavyweight tasks These are Lightweight tasks
Inter-process communication is expensive Inter-Thread communication is inexpensive.
and
limited
Context switching from one process to another Context switching is low cost in terms of
is memory,
costly in terms of memory because they run on the same address space
This is not under the control of Java This is controlled by Java
Advantage of the Multithreading
 It enables you to write very efficient programs that maximizes the CPU utilization and
reduces the idle time.
 Most I/O devices such as network ports, disk drives or the keyboard are much slower than CPU
 A program will spend much of it time just send and receive the information to or
from the devices, which in turn wastes the CPU valuable time.
 By using the multithreading, your program can perform another task during this idle time.
 For example, while one part of the program is sending a file over the internet, another
part can read the input from the keyboard, while other part can buffer the next block to
send.
 It is possible to run two or more threads in multiprocessor or multi core
systems simultaneously.

The States of the Thread


A thread can be in one of the several states. In general terms, a thread can running. It can
be ready to run as soon as it gets the CPU time. A running thread can be suspended, which is a
temporary halt to its execution. It can later be resumed. A thread can be blocked when waiting
for the resource. A thread can be terminated.
Single Threaded Program
A Thread is similar to simple program that contains single flow of control. It has
beginning, body, and ending. The statements in the body are executed in sequence.
For example:
class ABC //Begining

//Body

} //ending

Multithreaded Program
A unique property of the java is that it supports the multithreading. Java enables us the
multiple flows of control in developing the program. Each separate flow of control is thought
as tiny program known as "thread" that runs in parallel with other threads. In the following
example when the main thread is executing, it may call thread A, as the Thread A is in execution
again a call is mad for Thread B. Now the processor is switched from Thread A to Thread B.
After the task is finished the flow of control comes back to the Thread A. The ability of the
language that supports multiple threads is called "Concurrency". Since threads in the java are
small sub programs of the main program and share the same address space, they are called
"light weight processes".
Main
thread

Start
Start
Start
switch
Thread A
Thread B switch Thread C

The Main Thread


When a Java program starts up, one thread begins running immediately. This is usually called
the main thread of your program, because it is the one that is executed when your program
begins. The main thread is important for two reasons:
• It is the thread from which other “child” threads will be spawned.
• Often, it must be the last thread to finish execution because it performs various
shutdown actions.
Although the main thread is created automatically when your program is started, it can be
controlled through a Thread object. To do so, you must obtain a reference to it by calling the
method currentThread( ), which is a public static member of Thread. Its general form is
shown here:

static Thread.currentThread( )

This method returns a reference to the thread in which it is called. Once you have a reference
to the main thread, you can control it just like any other thread.
Let’s begin by reviewing the following example:
CurrentThreadDemo.java

// Controlling the main Thread


class CurrentThreadDemo {
public static void main(String args[]) {
// Get reference to the currently executing thread
Thread t = Thread.currentThread();
System.out.println("Current thread: " + t);

// Change the name of the current thread


t.setName("My Thread");
System.out.println("After name change: " + t);
try {
// Countdown from 5 to 1 with a 1-second pause
for (int n = 5; n > 0; n--) {
System.out.println(n);
Thread.sleep(1000); // pause for 1 second
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted");
}
}
}

Output

✅ Explanation:

 Thread.currentThread() returns a reference to the currently executing thread (in this case, the
main thread).
 The thread name is changed using setName("My Thread").
 Thread.sleep(1000) pauses the main thread for 1 second between each print of the countdown.
 If the thread is interrupted during sleep, an InterruptedException is caught and handled.

Creation of Thread
Creating the threads in the Java is simple. The threads can be implemented in the form of
object that contains a method "run()". The "run()" method is the heart and soul of any thread. It
makes up the entire body of the thread and is the only method in which the thread behavior can be
implemented.
There are two ways to create thread.
1. Declare a class that implements the Runnable interface which contains the run() method .
2. Declare a class that extends the Thread class and override the run() method.

1. Implementing the Runnable Interface


The Runnable interface contains the run() method that is required for implementing the
threads in our program. To do this we must perform the following steps:
I. Declare a class as implementing the Runnable interface
II. Implement the run() method
III. Create a Thread by defining an object that is instantiated from this "runnable" class as
the target of the thread
IV. Call the thread's start() method to run the thread.

Example program:
// STEP 1: Implement Runnable interface
class x implements Runnable {

// STEP 2: Define the run() method


public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println("The Thread x is: " + i);
}
System.out.println("End of the Thread x");
}
}

public class RunnableTest {


public static void main(String args[]) {
// STEP 3: Create an instance of class x
x r = new x();

// STEP 4: Pass it to a Thread object


Thread threadx = new Thread(r);

// STEP 5: Start the thread


threadx.start();

// STEP 6: Main thread output


System.out.println("The end of the main thread");
}
}
Output:
 Runnable is a functional interface with a single method run() used to define the task.
 This allows the class x to extend another class if needed (unlike extending Thread).
 Thread threadx = new Thread(r); wraps the runnable object into a thread.
 threadx.start(); triggers the run() method in a separate thread.
 The main() method finishes independently, and both threads can run in parallel.

2. Extending the thread class


We can make our thread by extending the Thread class of java.lang.Thread class. This
gives us access to all the methods of the Thread. It includes the following steps:
I. Declare the class as Extending the Thread class.
II. Override the "run()" method that is responsible for running the thread.
III. Create a thread and call the "start()" method to instantiate the Thread Execution.
Declaring the class
TheThread class can be declared as
follows: class MyThread extends
Thread
{

Overriding the method run()


The run() is the method of the Thread. We can override this as
follows: public void run()
{
}
Starting the new Thread
To actually to create and run an instance of the thread class, we must write the following:

MyThread a=new MyThread(); // creating the


Thread a.start(); // Starting the
Thread
Example program: ThreadTest.java

import java.io.*; import java.lang.*;

class A extends Thread


{

public void run()


{
for(int i=1;i<=5;i++)
{
System.out.println("From Threaad A :i="+i);
}
System.out.println("Exit from Thread A");
}
}

class B extends Thread


{
public void run()
{
for(int j=1;j<=5;j++)
{
System.out.println("From Threaad B :j="+j);
}
System.out.println("Exit from Thread B");
}
}

class C extends Thread


{
public void run()
{
for(int k=1;k<=5;k++)
{
System.out.println("From Threaad C :k="+k);
}
System.out.println("Exit from Thread C");
}
}
class ThreadTest
{
public static void main(String args[])
{
System.out.println("main thread
started"); A a=new A();
a.start();
B b=new B();

Output: First Run

Second Run: Produces different output in the second run, because of the processor switching
from one thread to other.
Creating Multiple Threads
So far, you have been using only two threads: the main thread and one child thread.
However, your program can spawn as many threads as it needs. For example, the following
program creates three child threads:

// Create multiple threads.

class NewThread implements Runnable


{
String name; // name of thread
Thread t;
NewThread(String threadname)
{
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start(); // Start the thread
}
// This is the entry point for thread.
public void run()
{
try
{
for(int i = 5; i > 0; i--)
{
System.out.println(name + ": " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e)
{
System.out.println(name + "Interrupted");
}
System.out.println(name + " exiting.");
} //end of run method
} //end of NewThread
class MultiThreadDemo
{
public static void main(String args[])
{
new NewThread("One"); // start threads
new NewThread("Two");
new
NewThread("Three"); try
{
// wait for other threads to end
Thread.sleep(10000);
}
catch (InterruptedException e)
{
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}

When a Thread is ended

It is often very important to know which thread is ended. This helps to prevent the main from
terminating before the child Thread is terminating. To address this problem "Thread" class
provides two methods: 1) Thread.isAlive() 2) Thread.join().

The general form of the "isAlive()" method is as follows:

final boolean isAlive();

This method returns the either "TRUE" or "FALSE" . It returns "TRUE" if the thread is alive,
returns "FALSE" otherwise.
While isAlive( ) is occasionally useful, the method that you will more commonly use to wait for
a thread to finish is called join( ), shown here:

final void join() throws InterruptedException


This method waits until the thread on which it is called terminates. Its name comes from the
concept of the calling thread waiting until the specified thread joins it. Additional forms of join (
) allow you to specify a maximum amount of time that you want to wait for the specified thread
to terminate.

Example Program:

// Using join() to wait for threads to finish

class NewThread implements Runnable {

String name; // name of thread

Thread t;

NewThread(String threadname) {

name = threadname;

t = new Thread(this, name); // FIXED: pass this Runnable to Thread

System.out.println("New thread: " + t.getName());

t.start(); // Start the thread

// This is the entry point for thread

public void run() {

try {

for (int i = 5; i > 0; i--) {

System.out.println(name + ": " + i);

Thread.sleep(1000);

} catch (InterruptedException e) {

System.out.println(name + " interrupted.");

System.out.println(name + " exiting.");


}

class DemoJoin {

public static void main(String args[]) {

NewThread ob1 = new NewThread("One");

NewThread ob2 = new NewThread("Two");

NewThread ob3 = new NewThread("Three");

System.out.println("Thread One is alive: " + ob1.t.isAlive());

System.out.println("Thread Two is alive: " + ob2.t.isAlive());

System.out.println("Thread Three is alive: " + ob3.t.isAlive());

// Wait for threads to finish

try {

System.out.println("Waiting for threads to finish.");

ob1.t.join();

ob2.t.join();

ob3.t.join();

} catch (InterruptedException e) {

System.out.println("Main thread Interrupted");

System.out.println("Thread One is alive: " + ob1.t.isAlive());

System.out.println("Thread Two is alive: " + ob2.t.isAlive());


System.out.println("Thread Three is alive: " + ob3.t.isAlive());

System.out.println("Main thread exiting.");

Output:

New thread: One

New thread: Two

New thread: Three

Thread One is alive: true

Thread Two is alive: true

Thread Three is alive: true

Waiting for threads to finish.

One: 5

Two: 5

Three: 5

...

Three exiting.

Thread One is alive: false

Thread Two is alive: false

Thread Three is alive: false

Main thread exiting.

This program demonstrates multithreading in Java using the Runnable interface, where three threads
are created and executed concurrently. The join() method ensures that the main thread waits for all
other threads ("One", "Two", and "Three") to finish before proceeding. It effectively shows how to
manage thread execution order and monitor thread status using isAlive().

THE THREAD PRIORITIES


Thread priorities are used by the thread scheduler to decide when and which thread should be
allowed to run. In theory, higher-priority threads get more CPU time than lower-priority
threads. In practice, the amount of CPU time that a thread gets often depends on several factors
besides its priority. (For example, how an operating system implements multitasking can affect
the relative availability of CPU time.) A higher-priority thread can also preempt a lower-priority
one. For instance, when a lower-priority thread is running and a higher-priority thread resumes
(from sleeping or waiting on I/O, for example), it will preempt the lower priority thread.

To set a thread’s priority, use the setPriority( ) method, which is a member of Thread.
This is its general form:

final void setPriority(int level)

Here, level specifies the new priority setting for the calling thread. The value of level must be
within the range MIN_PRIORITY and MAX_PRIORITY. Currently, these values are 1 and
10, respectively. To return a thread to default priority, specify NORM_PRIORITY, which is
currently 5. These priorities are defined as static final variables within Thread.
You can obtain the current priority setting by calling the getPriority( ) method of Thread,
shown here:
// Setting and Getting Thread Priorities

class PThread1 extends Thread {


public void run() {
System.out.println("Child 1 is started");
}
}

class PThread2 extends Thread {


public void run() {
System.out.println("Child 2 is started");
}
}

class PThread3 extends Thread {


public void run() {
System.out.println("Child 3 is started");
}
}

public class PTest {


public static void main(String args[]) {
// Create threads
PThread1 pt1 = new PThread1();
PThread2 pt2 = new PThread2();
PThread3 pt3 = new PThread3();

// Set priorities (1 = MIN_PRIORITY, 10 = MAX_PRIORITY)


pt1.setPriority(1); // Low priority
pt2.setPriority(9); // High priority
pt3.setPriority(6); // Medium priority

// Start threads
pt1.start();
pt2.start();
pt3.start();

// Get and print the priority of pt1


System.out.println("The pt1 thread priority is: " + pt1.getPriority());
}
}

Output:
Child 1 is started
Child 2 is started
Child 3 is started
The pt1 thread priority is: 1
Explanation:
This program creates three threads and assigns different priorities using setPriority(). The start()
method begins their execution, and the getPriority() method is used to retrieve and display the priority
of one thread (pt1). Although priorities suggest execution order (higher runs first), actual scheduling is
JVM-dependent, so results may vary.

SYNCHRONIZATION

When two or more threads need access to a shared resource, they need some way to
ensure that the resource will be used by only one thread at a time. The process by which this is
achieved is called synchronization.

Key to synchronization is the concept of the monitor (also called a semaphore). A


monitor is an object that is used as a mutually exclusive lock, or mutex. Only one thread can own
a monitor at a given time. When a thread acquires a lock, it is said to have entered the monitor.
All other threads attempting to enter the locked monitor will be suspended until the first thread
exits the monitor. These other threads are said to be waiting for the monitor. A thread that owns a
monitor can reenter the same monitor if it so desires.

Let us try to understand the problem without synchronization. Here, in the following example to
threads are accessing the same resource (object) to print the Table. The Table class contains one
method, printTable(int ), which actually prints the table. We are creating two Threads, Thread1
and Thread2, which are using the same instance of the Table Resource (object), to print the table.
When one thread is using the resource, no other thread is allowed to access the same resource
Table to print the table.

Example without the synchronization:


// Program to demonstrate multithreading WITHOUT synchronization

class Table {
// Not synchronized: multiple threads can access simultaneously
void printTable(int n) {
for (int i = 1; i <= 5; i++) {
System.out.println(n * i);
try {
Thread.sleep(400);
} catch (InterruptedException ie) {
System.out.println("Exception: " + ie);
}
}
}
}

class MyThread1 extends Thread {


Table t;

MyThread1(Table t) {
this.t = t;
}

public void run() {


t.printTable(5); // prints table of 5
}
}

class MyThread2 extends Thread {


Table t;

MyThread2(Table t) {
this.t = t;
}

public void run() {


t.printTable(100); // prints table of 100
}
}

public class TestWithoutSynchronization {


public static void main(String[] args) {
Table obj = new Table(); // shared resource

MyThread1 t1 = new MyThread1(obj);


MyThread2 t2 = new MyThread2(obj);

t1.start();
t2.start();
}
}
Output :
5
100
10
200
15
300
20
400
25
500

This Java program demonstrates what happens when multiple threads access a shared resource without
synchronization. The class Table contains a method printTable(int n) that prints the multiplication
table of a given number. Two thread classes, MyThread1 and MyThread2, are created. They both receive a
shared object of the Table class and call the printTable() method with different values: 5 and 100.

However, the printTable() method is not marked with the synchronized keyword, meaning there is no
control over how the threads access this method. When t1.start() and t2.start() are called, the threads
run independently and simultaneously. As a result, the JVM may switch between them at any moment,
leading to interleaved or mixed output on the console. This kind of output is unpredictable and varies with
each program run.

Using the Java synchronized method

If you declare any method as synchronized, it is known as synchronized method. Synchronized


method is used to lock an object for any shared resource. When a thread invokes a synchronized
method, it automatically acquires the lock for that object and releases it when the thread
completes its task.
The general form of the synchronized method is:
synchronized type method_name(para_list)
{
//body of the method
}
where synchronized is the keyword, method contains the type, and method_name represents the
name of the method, and para_list indicate the list of the parameters.

Example using the synchronized method

// Program to demonstrate synchronization using synchronized method

class Table {
// Synchronized method to avoid thread interference
synchronized void printTable(int n) {
for (int i = 1; i <= 5; i++) {
System.out.println(n * i);
try {
Thread.sleep(400); // delay to simulate thread execution
} catch (InterruptedException ie) {
System.out.println("The Exception is: " + ie);
}
}
} // end of printTable()
}

class MyThread1 extends Thread {


Table t;

MyThread1(Table t) {
this.t = t;
}

public void run() {


t.printTable(5); // calling synchronized method
}
}

class MyThread2 extends Thread {


Table t;

MyThread2(Table t) {
this.t = t;
}

public void run() {


t.printTable(100); // calling synchronized method
}
}

public class TestSynchronization1 {


public static void main(String[] args) {
Table obj = new Table(); // shared object

MyThread1 t1 = new MyThread1(obj);


MyThread2 t2 = new MyThread2(obj);

t1.start();
t2.start();
}
}

The output for the above program will be as follow:


Output:
5
10
15
20
25
100
200
300
400
500

This The threads will now execute one after the other, not simultaneously, because of synchronization on the
printTable() method.

Java program demonstrates how to prevent thread interference by using the synchronized keyword
when multiple threads access a shared resource. The program defines a class Table with a method
printTable(int n) that prints the multiplication table of a given number. Two thread classes, MyThread1
and MyThread2, are created to call this method with different values (5 and 100).

In the original version, the printTable() method was not synchronized, so both threads could access and
execute it at the same time, leading to interleaved output. To fix this issue, the method is marked as
synchronized, which ensures that only one thread can execute the method at any given time on the
shared object.

In the main() method, a single instance of Table is created and shared by both threads. When the first
thread starts and enters the printTable() method, the second thread must wait until the first one
completes. This behavior guarantees clean and ordered output. The use of Thread.sleep(400) introduces
a small delay, making the thread switching and synchronization effects more visible.

Note:

1. This way of communications between the threads competing for same resource is
called implicit communication.
2. This has one disadvantage due to polling. The polling wastes the CPU time. To save the
CPU time, it is preferred to go to the inter-thread communication.

INTER-THREAD COMMUNICATION
If two or more Threads are communicating with each other, it is called "inter thread"
communication. Using the synchronized method, two or more threads can communicate
indirectly. Through, synchronized method, each thread always competes for the resource. This
way of competing is called polling. The polling wastes the much of the CPU valuable time. The
better solution to this problem is, just notify other threads for the resource, when the current
thread has finished its task. This is explicit communication between the threads.

Java addresses this polling problem, using via wait(), notify(), and notifyAll() methods. These
methods are implemented as final methods in Object, so all classes have them. All three
methods can be called only from within a synchronized context.

 wait( ) tells the calling thread to give up the monitor and go to sleep until
some other thread enters the same monitor and calls notify( ).
 notify( ) wakes up a thread that called wait( ) on the same object.
 notifyAll( ) wakes up all the threads that called wait( ) on the same object. One
of the threads will be granted access.

These methods are declared within Object, as shown here:

final void wait( ) throws InterruptedException


final void notify( )
final void notifyAll( )
Additional forms of wait( ) exist that allow you to specify a period of time to wait.

Although wait( ) normally waits until notify( ) or notifyAll( ) is called, there is a


possibility that in very rare cases the waiting thread could be awakened due to a spurious
wakeup. In this case, a waiting thread resumes without notify( ) or notifyAll( ) having been
called. (In essence, the thread resumes for no apparent reason.) Because of this remote
possibility, Sun recommends that calls to wait( ) should take place within a loop that checks the
condition on which the thread is waiting. The following example shows this technique.

Example program for producer and consumer

problem // Shared resource

class Q {

int n;

boolean valueSet = false;

// Consumer calls this method

synchronized int get() {

while (!valueSet) {

try {

wait(); // Wait until value is available

} catch (InterruptedException e) {

System.out.println("InterruptedException

caught");

}
System.out.println("Got: " + n);

valueSet = false;

notify(); // Notify producer that the value is

consumed

return n;

// Producer calls this method

synchronized void put(int n) {

while (valueSet) {

try {

wait(); // Wait until value is consumed

} catch (InterruptedException e) {

System.out.println("InterruptedException

caught");

this.n = n;

valueSet = true;

System.out.println("Put: " + n);

notify(); // Notify consumer that value is available

// Producer thread
class Producer implements Runnable {

Q q;

Producer(Q q) {

this.q = q;

new Thread(this, "Producer").start();

public void run() {

int i = 0;

while (true) {

q.put(i++);

// Consumer thread

class Consumer implements Runnable {

Q q;

Consumer(Q q) {

this.q = q;

new Thread(this, "Consumer").start();

public void run() {

while (true) {
q.get();

// Main class

public class PCFixed {

public static void main(String[] args) {

Q q = new Q(); // Shared object

new Producer(q); // Start producer thread

new Consumer(q); // Start consumer

thread

System.out.println("Press Ctrl+C to stop.");

Output:

Put: 0

Got: 0

Put: 1

Got: 1

Put: 2

Got: 2

This program demonstrates the Producer–Consumer problem using inter-thread communication in


Java. It consists of three main components: a shared resource class Q, a Producer thread, and a Consumer
thread. The shared class Q contains an integer variable n and a boolean flag valueSet to indicate whether a
value has been produced but not yet consumed.
The put() method is used by the Producer to set a value. Before putting the value, it checks if valueSet is
already true (i.e., the previous value has not been consumed). If so, it waits using the wait() method.
Once the consumer consumes the value and resets valueSet to false, the producer is notified and
continues execution. Similarly, the get() method is used by the Consumer to retrieve the value. If
valueSet is false (i.e., no value is available), it waits. Once the producer puts a new value and sets
valueSet to true, it notifies the consumer.

Both threads run in infinite loops, simulating a continuous production and consumption of values. The
synchronized keyword ensures that only one thread accesses put() or get() at a time, avoiding race
conditions. The wait() and notify() methods allow the threads to coordinate, ensuring that the Producer
doesn’t overwrite unconsumed data and the Consumer doesn’t read before data is available.

Suspending, Blocking and Stopping Threads

Whenever we want stop a thread we can stop from running using "stop()" method of thread
class. It's general form will be as follows:
Thread.stop();
This method causes a thread to move from running to dead state. A thread will also move to
dead state automatically when it reaches the end of its method.
Blocking Thread
A thread can be temporarily suspended or blocked from entering into the runnable and running
state by using the following methods:
sleep() —blocked for specified time
suspend() ----blocked until further orders
wait() --blocked until certain condition occurs
These methods cause the thread to go into the blocked state. The thread will return to the
runnable state when the specified time is elapsed in the case of sleep(), the resume() method is
invoked in the case of suspend(), and the notify() method is called in the case of wait().

Example program:
The following program demonstrates these methods:
// Using suspend() and resume().

class NewThread implements Runnable


{
String name; // name of thread
Thread t;
NewThread(String threadname)
{ name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start(); // Start the thread
}
// This is the entry point for thread.
public void run()
{
try {
for(int i = 15; i > 0; i--)
{
System.out.println(name + ": " + i);
} Thread.sleep(200);
}
catch (InterruptedException e)
{
System.out.println(name + " interrupted.");
}
System.out.println(name + " exiting.");
}
}
class SuspendResume
{
public static void main(String args[])
{
NewThread ob1 = new NewThread("One");
NewThread ob2 = new NewThread("Two");
try
{
Thread.sleep(1000); ob1.t.suspend();
System.out.println("Suspending thread One");
Thread.sleep(1000);
ob1.t.resume(); System.out.println("Resuming
thread One"); ob2.t.suspend();
System.out.println("Suspending thread Two");
Thread.sleep(1000);
ob2.t.resume();
System.out.println("Resuming thread
Two");
}
catch (InterruptedException e)
{
System.out.println("Main thread Interrupted");
}
// wait for threads to finish
try
{
System.out.println("Waiting for threads to finish.");
ob1.t.join();
ob2.t.join();
}
catch (InterruptedException e)
{
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}

Output:
New thread: Thread[One,5,main]
New thread: Thread[Two,5,main]
One: 15
Two: 15
One: 14
Two: 14
One: 13
Two: 13
Suspending thread One
Two: 12
Two: 11
Two: 10
Resuming thread One
Suspending thread Two
One: 12
One: 11
One: 10
Resuming thread Two
Two: 9
One: 9
Two: 8
One: 8
...
Main thread exiting.

 Two threads One and Two are created from the NewThread class.
 Both threads print a countdown from 15 to 1 with a 200ms delay.

 In main(), after a delay:

 Thread One is suspended temporarily using suspend(), halting its execution.


 Thread Two continues to run.
 After another delay, Thread One is resumed using resume().
 Then Thread Two is suspended and resumed similarly.

 Finally, the main() thread waits for both child threads to finish using join() and exits.

LIFE CYCLE OF A THREAD


During the life time of the thread, there are many states it can enter. They include the following:
 Newborn state
 Runnable State
 Running State
 Blocked state
 Dead state
A thread can always in any one of the five states. It can move from one state to other via
variety of ways as shown in the fig.
Newborn state Stop

Start

Stop Dead State

Fig: Life Cycle of Running Runnable


Thread State State
Yield
Stop

suspend() resume()
sleep() notify()
wait()

Blocked State
Newborn State: When we create a thread it is said to be in the new born state. At this state we
can do the following:
 schedule it for running using the start() method.
 Kill it using stop() method.

Runnable State: A runnable state means that a thread is ready for execution and waiting for the
availability of the processor. That is the thread has joined the queue of the threads for execution.
If all the threads have equal priority, then they are given time slots for execution in the round
robin fashion, first-come, first-serve manner. The thread that relinquishes the control will join the
queue at the end and again waits for its turn. This is known as time slicing.
Running State:

Running state: Running state means that the processor has given its time to the thread for it
execution. The thread runs until it relinquishes the control or it is preempted by the other higher
priority thread. As shown in the fig. a running thread can be preempted using the suspend(), or
wait(), or sleep() methods.

Blocked state: A thread is said to be in the blocked state when it is prevented from entering into
runnable state and subsequently the running state.

Dead state: Every thread has a life cycle. A running thread ends its life when it has completed
execution. It is a natural death. However we also can kill the thread by sending the stop()
message to it at any time.

The Thread Methods:

Sl No Method Name Description


1 run() Used to write the body of the thread
2 start() Used to start the thread
3 sleep() Used to make the thread sleep for milliseconds
4 suspend() Used to suspend a running thread
5 wait() Waits until further ordered
6 yield Used to give control to other thread before it turn comes
7 Stop() Used to stop the thread
8 resume() Used to start the blocked thread
9 notify() Used to notify the waiting thread
10 notifyAll() Used to notify the all waiting threads

Thread Exceptions
Note that a call to the sleep() method is always enclosed in try/ catch block. This is necessary
because the sleep() method throws an exception, which should be caught. If we fail to catch the
exception the program will not compile.

it general form will be as follows:

try }
{
Th
rea
d.s
lee
p(
10
00
);
catch(Exception e)
{

}
Deadlock
Deadlock in java is a part of multithreading. Deadlock can occur in a situation when a thread
is waiting for an object lock, that is acquired by another thread and second thread is waiting for
an object lock that is acquired by first thread. Since, both threads are waiting for each other to
release the lock, the condition is called deadlock.

Thread1 Thread2
Y

Here, in the above figure, the resource X is held by Thread1, and at the same time the Thread1 is
trying to access the resource which is held by the Thread2. This is causing the circular
dependency between two Threads. This is called, Deadlock.

Example program:
TestDead.java
public class TestDead
{
public static void main(String[] args)
{
final String resource1 = "John Gardner";
final String resource2 = "James Gosling";
// t1 tries to lock resource1 then resource2

Thread t1 = new Thread()


{
public void run()
{
synchronized (resource1)
{
System.out.println("Thread 1: locked resource 1");

try {
Thread.sleep(100);
}
catch (Exception e)
{
System.out.println(e);
}

synchronized (resource2)
{
System.out.println("Thread 1: locked resource 2");
}
}
} //end of run()
}; //end of t1

// t2 tries to lock resource2 then resource1


Thread t2 = new Thread()
{
public void run()
{
synchronized (resource2)
{
System.out.println("Thread 2: locked resource 2");

try { Thread.sleep(100);} catch (Exception e) {}

synchronized (resource1)
{
System.out.println("Thread 2: locked resource 1");
}
}
}//end of run()
}; //end of t2
t1.start();
t2.start();
}
}

Output:
GENERICS IN JAVA

Generics means parameterized types. The idea is to allow a type (like Integer, String, etc., or user-defined
types) to be a parameter to methods, classes, and interfaces. Generics in Java allow us to create classes,
interfaces, and methods where the type of the data is specified as a parameter. If we use generics, we do not
need to write multiple versions of the same code for different data types.

Types of Java Generics

1. Generic Method: A generic Java method takes a parameter and returns some value after performing a
task. It is exactly like a normal function, however, a generic method has type parameters that are cited by an
actual type. This allows the generic method to be used in a more general way. The compiler takes care of the
type of safety, which enables programmers to code easily since they do not have to perform long, individual
type castings.
2. Generic Classes: A generic class is implemented exactly like a non-generic class. The only difference is
that it contains a type parameter section. There can be more than one type of parameter, separated by a
comma. The classes that accept one or more parameters are known as parameterized classes or
parameterized types.
Generic Class
A generic class is a class that can operate on objects of different types using a type parameter. Like C++, we
use <> to specify parameter types in generic class creation. To create objects of a generic class, we use the
following syntax:
// To create an instance of generic class

BaseType <Type> obj = new BaseType <Type>()

Note: In Parameter type, we can not use primitives like "int", "char", or "double". Use wrapper classes
like Integer, Character, etc.
// Java program to show working of user defined
// Generic classes

// We use < > to specify Parameter type


class Test<T> {

// An object of type T is declared


T obj;
Test(T obj) { this.obj = obj; } // constructor
public T getObject() { return this.obj; }
}

// Driver class to test above


class Geeks {
public static void main(String[] args)
{
// instance of Integer type
Test<Integer> iObj = new Test<Integer>(15);
System.out.println(iObj.getObject());

// instance of String type


Test<String> sObj
= new Test<String>("java programming");
System.out.println(sObj.getObject());
}
}

// Java program to show working of user defined


// Generic classes

// We use < > to specify Parameter type


class Test<T> {

// An object of type T is declared


T obj;
Test(T obj) { this.obj = obj; } // constructor
public T getObject() { return this.obj; }
}

// Driver class to test above


class Geeks {
public static void main(String[] args)
{
// instance of Integer type
Test<Integer> iObj = new Test<Integer>(15);
System.out.println(iObj.getObject());

// instance of String type


Test<String> sObj
= new Test<String>("Java Programming");
System.out.println(sObj.getObject());
}
}

Output
15
Java Programming

This program demonstrates the concept of Java Generics by defining a user-defined generic class
Test<T>. The type T is a type parameter which will be replaced with an actual data type when an object of
Test is created.

 The class has one member: T obj, where T can be any data type (Integer, String, etc.).
 The constructor Test(T obj) initializes the object.
 The method getObject() returns the value of obj.
In the main() method of class Geeks, two instances of the generic class Test are created:

 One with Integer as the type parameter (value = 15)


 One with String as the type parameter (value = "Java Programming")

We can also pass multiple Type parameters in Generic classes.


Example: Generic Class with Multiple Type Parameters

// Java program to show multiple


// type parameters in Java Generics

// We use < > to specify Parameter type


class Test<T, U>
{
T obj1; // An object of type T
U obj2; // An object of type U

// constructor
Test(T obj1, U obj2)
{
this.obj1 = obj1;
this.obj2 = obj2;
}

// To print objects of T and U


public void print()
{
System.out.println(obj1);
System.out.println(obj2);
}
}

// Driver class to test above


class Geeks
{
public static void main (String[] args)
{
Test <String, Integer> obj =
new Test<String, Integer>("GfG", 15);

obj.print();
}
}

Output
GfG
15
Generic Method
We can also write generic methods that can be called with different types of arguments based on the type of
arguments passed to the generic method. The compiler handles each method.
// Java program to show working of user defined
// Generic functions
class Geeks {

// A Generic method example


static <T> void genericDisplay(T element)
{
System.out.println(element.getClass().getName() + " = " + element);
}

// Driver method
public static void main(String[] args)
{
// Calling generic method with Integer argument
genericDisplay(11);

// Calling generic method with String argument


genericDisplay("GeeksForGeeks");

// Calling generic method with double argument


genericDisplay(1.0);
}
}

Output
java.lang.Integer = 11
java.lang.String = GeeksForGeeks
java.lang.Double = 1.0

Limitations of Generics
1. Generics Work Only with Reference Types
When we declare an instance of a generic type, the type argument passed to the type parameter must be a
reference type. We cannot use primitive data types like int, char.
Test<int> obj = new Test<int>(20);
The above line results in a compile-time error that can be resolved using type wrappers to encapsulate a
primitive type.
But primitive type arrays can be passed to the type parameter because arrays are reference types.
ArrayList<int[]> a = new ArrayList<>();
2. Generic Types Differ Based on their Type Arguments
During compilation, generic type information is erased which is also known as type erasure.
Example:
// Java program to show working
// of user-defined Generic classes

// We use < > to specify Parameter type


class Test<T> {
// An object of type T is declared
T obj;
Test(T obj) { this.obj = obj; } // constructor
public T getObject() { return this.obj; }
}

// Driver class to test above


class Geeks {
public static void main(String[] args)
{
// instance of Integer type
Test<Integer> iObj = new Test<Integer>(15);
System.out.println(iObj.getObject());

// instance of String type


Test<String> sObj
= new Test<String>("GeeksForGeeks");
System.out.println(sObj.getObject());
iObj = sObj; // This results an error
}
}

Output:
error:
incompatible types:
Test cannot be converted to Test

Explanation: Even though iObj and sObj are of type Test, they are the references to different types because
their type parameters differ. Generics add type safety through this and prevent errors.

Type Parameter Naming Conventions


The type parameters naming conventions are important to learn generics thoroughly. The common type
parameters are as follows:
 T: Type
 E: Element
 K: Key
 N: Number
 V: Value
Advantages of Generics
 Code Reusability: We can write a method, class, or interface once and use it with any type.
 Type Safety: Generics ensure that errors are detected at compile time rather than runtime, promoting
safer code.
 No Need for Type Casting: The compiler automatically handles casting, removing the need for explicit
type casting when retrieving data.
 Code Readability and Maintenance: By specifying types, code becomes easier to read and maintain.
 Generic Algorithms: Generics allow for the implementation of algorithms that work across various
types, promoting efficient coding practices.
Disadvantages of Generics
 Complexity: For beginners, understanding concepts like wildcards (? extends, ? super) can be difficult.
 Performance Overhead: Type erasure causes some overhead as generic types are converted
to Object during runtime.
 No Support for Primitive Types: Generics only work with reference types, requiring the use of
wrapper classes like Integer or Double for primitives.
 Limited Reflection: Type erasure limits how much you can use reflection with generics since type
information is not available at runtime.

Benefits of Generics:

Programs that use Generics has got many benefits over non-generic code.
1. Code Reuse: We can write a method/class/interface once and use it for any type we want.
2. Type Safety: Generics make errors to appear compile time than at run time (It's always better to know
problems in your code at compile time rather than making your code fail at run time).
Suppose you want to create an ArrayList that store name of students, and if by mistake the programmer adds
an integer object instead of a string, the compiler allows it. But, when we retrieve this data from ArrayList,
it causes problems at runtime.
Example: Without Generics
// Java program to demonstrate that NOT using
// generics can cause run time exceptions
import java.util.*;

class Geeks
{
public static void main(String[] args)
{
// Creatinga an ArrayList without any type specified
ArrayList al = new ArrayList();

al.add("Sweta");
al.add("Gudly");
al.add(10); // Compiler allows this

String s1 = (String)al.get(0);
String s2 = (String)al.get(1);

// Causes Runtime Exception


String s3 = (String)al.get(2);
}
}

Output :
Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String
at Test.main(Test.java:19)

Here, we get runtime error.

How do Generics Solve this Problem?


When defining ArrayList, we can specify that this list can take only String objects.
Example: With Generics
// Using Java Generics converts run time exceptions into
// compile time exception.
import java.util.*;

class Geeks
{
public static void main(String[] args)
{
// Creating a an ArrayList with String specified
ArrayList <String> al = new ArrayList<String> ();

al.add("Sweta");
al.add("Gudly");

// Now Compiler doesn't allow this


al.add(10);

String s1 = (String)al.get(0);
String s2 = (String)al.get(1);
String s3 = (String)al.get(2);
}
}
Output:
15: error: no suitable method found for add(int)
al.add(10);
^

3. Individual Type Casting is not needed: If we do not use generics, then, in the above example, every
time we retrieve data from ArrayList, we have to typecast it. Typecasting at every retrieval operation is a big
headache. If we already know that our list only holds string data, we need not typecast it every time.
Example:
// We don't need to typecast individual members of ArrayList
import java.util.*;

class Geeks {
public static void main(String[] args)
{
// Creating a an ArrayList with String specified
ArrayList<String> al = new ArrayList<String>();

al.add("Sweta");
al.add("Gudly");

// Typecasting is not needed


String s1 = al.get(0);
String s2 = al.get(1);
}
}

4. Generics Promotes Code Reusability: With the help of generics in Java, we can write code that will
work with different types of data. For example,
Let's say we want to Sort the array elements of various data types like int, char, String etc. Basically we will
be needing different functions for different data types. For simplicity, we will be using Bubble sort.
But by using Generics, we can achieve the code reusability feature.

public class Geeks {

public static void main(String[] args)


{

Integer[] a = { 100, 22, 58, 41, 6, 50 };

Character[] c = { 'v', 'g', 'a', 'c', 'x', 'd', 't' };

String[] s = { "Amiya", "Kuna", "Gudly", "Sweta","Mama", "Rani", "Kandhei" };

System.out.print("Sorted Integer array: ");


sort_generics(a);

System.out.print("Sorted Character array: ");


sort_generics(c);

System.out.print("Sorted String array: ");


sort_generics(s);

public static <T extends Comparable<T> > void sort_generics(T[] a)


{
//As we are comparing the Non-primitive data types
//we need to use Comparable class

//Bubble Sort logic


for (int i = 0; i < a.length - 1; i++) {
for (int j = 0; j < a.length - i - 1; j++) {

if (a[j].compareTo(a[j + 1]) > 0) {

swap(j, j + 1, a);
}
}
}

// Printing the elements after sorted


for (T i : a)
{
System.out.print(i + ", ");
}
System.out.println();

public static <T> void swap(int i, int j, T[] a)


{
T t = a[i];
a[i] = a[j];
a[j] = t;
}

Output
Sorted Integer array: 6, 22, 41, 50, 58, 100,
Sorted Character array: a, c, d, g, t, v, x,
Sorted String array: Amiya, Gudly, Kandhei, Kuna, Mama, Rani, Sweta,

JDBC (Java Database Connectivity)


JDBC is an API that helps applications to communicate with databases, it allows Java programs to connect
to a database, run queries, retrieve, and manipulate data. Because of JDBC, Java applications can easily
work with different relational databases like MySQL, Oracle, PostgreSQL, and more.
JDBC Architecture
Explanation:
 Application: It can be a Java application or servlet that communicates with a data source.
 The JDBC API: It allows Java programs to execute SQL queries and get results from the database.
Some key components of JDBC API include
o Interfaces like Driver, ResultSet, RowSet, PreparedStatement, and Connection that helps
managing different database tasks.
o Classes like DriverManager, Types, Blob, and Clob that helps managing database
connections.
 DriverManager: It plays an important role in the JDBC architecture. It uses some database-specific
drivers to effectively connect enterprise applications to databases.
 JDBC drivers: These drivers handle interactions between the application and the database.

The JDBC architecture consists of two-tier and three-tier processing models to access a database. They are
as described below:
1. Two-Tier Architecture
A Java Application communicates directly with the database using a JDBC driver. It sends queries to the
database and then the result is sent back to the application. For example, in a client/server setup, the user's
system acts as a client that communicates with a remote database server.
Structure:
Client Application (Java) -> JDBC Driver -> Database
2. Three-Tier Architecture
In this, user queries are sent to a middle-tier services, which interacts with the database. The database results
are processed by the middle tier and then sent back to the user.
Structure:
Client Application -> Application Server -> JDBC Driver -> Database
JDBC Components
There are generally 4 main components of JDBC through which it can interact with a database. They are
as mentioned below:
1. JDBC API
It provides various methods and interfaces for easy communication with the database. It includes two key
packages
 java.sql: This package, is the part of Java Standard Edition (Java SE) , which contains the core
interfaces and classes for accessing and processing data in relational databases. It also provides essential
functionalities like establishing connections, executing queries, and handling result sets
 javax.sql: This package is the part of Java Enterprise Edition (Java EE) , which extends the
capabilities of java.sql by offering additional features like connection pooling, statement pooling, and
data source management.
It also provides a standard to connect a database to a client application.
2. JDBC Driver Manager
Driver manager is responsible for loading the correct database-specific driver to establish a connection with
the database. It manages the available drivers and ensures the right one is used to process user requests and
interact with the database.
3. JDBC Test Suite
It is used to test the operation (such as insertion, deletion, updating) being performed by JDBC Drivers.
4. JDBC Drivers
JDBC drivers are client-side adapters (installed on the client machine, not on the server) that convert
requests from Java programs to a protocol that the DBMS can understand. There are 4 types of JDBC
drivers:
1. Type-1 driver or JDBC-ODBC bridge driver
2. Type-2 driver or Native-API driver (partially java driver)
3. Type-3 driver or Network Protocol driver (fully java driver)
4. Type-4 driver or Thin driver (fully java driver) - It is a widely used driver. The older drivers like (JDBC-
ODBC) bridge driver have been deprecated and no longer supported in modern versions of Java.
JDBC Classes and Interfaces
Class/Interfaces Description

DriverManager Manages JDBC drivers and establishes database connections.

Connection Represents a session with a specific database.

Statement Used to execute static SQL queries.

Precompiled SQL statement, used for dynamic queries with


PreparedStatement
parameters.
Class/Interfaces Description

CallableStatement Used to execute stored procedures in the database.

Represents the result set of a query, allowing navigation


ResultSet
through the rows.

SQLException Handles SQL-related exceptions during database operations.

Steps to Connect to MySQL Database Using JDBC

Step 1: Load the JDBC Driver


Class.forName("com.mysql.cj.jdbc.Driver");

Step 2: Establish a Connection

Connection connection = DriverManager.getConnection(


"jdbc:mysql://localhost:3306/your_database",
"your_username",
"your_password"
);

Step 3: Create a Statement


Statement statement = connection.createStatement();

Step 4: Execute a Query


String query = "INSERT INTO students (id, name) VALUES (101, 'John Doe')";
int rowsAffected = statement.executeUpdate(query);
System.out.println("Rows affected: " + rowsAffected);

Step 5: Close the Connection


statement.close();
connection.close();

Create a Simple JDBC Application


The below Java program demonstrates how to establish a MYSQL database connection using JDBC and
execute a query.
// Java program to implement a simple JDBC application
import java.sql.*;

public class Geeks {


public static void main(String[] args)
{
// Database URL, username, and password

// Replace with your database name


String url
= "jdbc:mysql://localhost:3306/your_database";

// Replace with your MySQL username


String username = "your_username";

// Replace with your MySQL password


String password = "your_password";

// Updated query syntax for modern databases


String query
= "INSERT INTO students (id, name) VALUES (109, 'bhatt')";

// Establish JDBC Connection


try {

// Load Type-4 Driver


// MySQL Type-4 driver class
Class.forName("com.mysql.cj.jdbc.Driver");

// Establish connection
Connection c = DriverManager.getConnection(
url, username, password);

// Create a statement
Statement st = c.createStatement();

// Execute the query


int count = st.executeUpdate(query);
System.out.println(
"Number of rows affected by this query: "
+ count);

// Close the connection


st.close();
c.close();
System.out.println("Connection closed.");
}
catch (ClassNotFoundException e) {
System.err.println("JDBC Driver not found: "
+ e.getMessage());
}
catch (SQLException e) {
System.err.println("SQL Error: "
+ e.getMessage());
}
}
}
Output:

Note: When the program runs successfully, a new record is added to the students table as shown below:

Key Features of JDBC:


 Platform Independence: JDBC can perform database operation on any platform
 Standard API: It provides different ways to interact with different databases.
 Support for Multiple Databases: JDBC provide support to work with different databases like MySQL,
PostgreSQL, Oracle, etc.

You might also like