Download as pdf or txt
Download as pdf or txt
You are on page 1of 15

Distributed system Course Lab

Department of SE and IT
4th Level
Second Lab : Synchronization
2022 – 2023
Prepared by
Eng. Salah Alssayani
Synchronization
Developing multithreaded applications is much easier when threads don’t interact, typically via shared
variables. When interaction occurs, various problems can arise that make an application thread-unsafe
(incorrect in a multithreaded context). In this LAB II, you’ll learn about these problems and also learn
how to overcome them through the correct use of Java’s synchronization-oriented language features.

The Problems with Threads


Java’s support for threads facilitates the development of responsive and scalable applications. However,
this support comes at the price of increased complexity. Without care, your code can become riddled
with hard-to-find bugs related to race conditions, data races, and cached variables.

Race Conditions
A race condition occurs when the correctness of a computation depends on the relative timing or
interleaving of multiple threads by the scheduler.
There is no problem with this code fragment in a single-threaded context, and there is no problem in a
multithreaded context when data are local variables.

class Counter implements Runnable {


private static int c = 0;
public void increment () {
try{
Thread.sleep (10);
}
catch (InterruptedException e) {
e.printStackTrace ();
}
c++;
}
public void decrement () {
c--;
}
public int getValue () {
return c;
}
@Override public void run ()
{
//incrementing
increment ();
System.out.println ("Value for Thread After increment " +
Thread.currentThread ().getName () + " " +
this.getValue ());
//decrementing
decrement ();
System.out.println ("Value for Thread at last " +
Thread.currentThread ().getName () + " " +
this.getValue ());
}
}
public class Main{
public static void main (String args[]) {
Counter counter = new Counter ();
Thread t1 = new Thread (counter, "Thread-1");
Thread t2 = new Thread (counter, "Thread-2");
Thread t3 = new Thread (counter, "Thread-3");
t1.start ();
t2.start ();
t3.start ();
}
}

Avoiding Race Conditions


class Counter implements Runnable
{
private static int c = 0;
public void increment ()
{
try
{
Thread.sleep (10);
}
catch (InterruptedException e)
{
e.printStackTrace ();
}
c++;
}
public void decrement ()
{
c--;
}
public int getValue ()
{
return c;
}
@Override public void run ()
{
synchronized (this)
{
// incrementing
increment ();
System.out.println ("Value for Thread After increment " +
Thread.currentThread ().getName () + " " +
this.getValue ());
//decrementing
decrement ();
System.out.println ("Value for Thread at last " +
Thread.currentThread ().getName () + " " +
this.getValue ());
}
}
}

public class Main{


public static void main (String args[]) {
Counter counter = new Counter ();
Thread t1 = new Thread (counter, "Thread-1");
Thread t2 = new Thread (counter, "Thread-2");
Thread t3 = new Thread (counter, "Thread-3");
t1.start ();
t2.start ();
t3.start ();
}
}

Data Races
A race condition is often confused with a data race in which two or more threads (in a single
application) access the same memory location concurrently, at least one of the accesses is for writing,
and these threads don’t coordinate their accesses to that memory. When these conditions hold, access
order is non-deterministic. Different results may be generated from run to run, depending on that order.
Cached Variables
To boost performance, the compiler, the Java virtual machine (JVM), and the operating system can
collaborate to cache a variable in a register or a processor-local cache, rather than rely on main
memory. Each thread has its own copy of the variable. When one thread writes to this variable, it’s
writing to its copy; other threads are unlikely to see the update in their copies.

import java.math.BigDecimal;

public class JoinDemo{


// constant used in pi computation

private static final BigDecimal FOUR = BigDecimal.valueOf(4);

// rounding mode to use during pi computation

private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;

private static BigDecimal result;

public static void main(String[] args)


{
Runnable r = () ->
{
synchronized(FOUR)
{
result = computePi(50000);
}
};
Thread t = new Thread(r);
t.start();
try
{
t.join();
}
catch (InterruptedException ie)
{
// Should never arrive here because interrupt() is never
// called.
}
synchronized(FOUR)
{
System.out.println(result);
}
}

/*
* Compute the value of pi to the specified number of digits after the
* decimal point. The value is computed using Machin's formula:
*
* pi/4 = 4*arctan(1/5)-arctan(1/239)
*
* and a power series expansion of arctan(x) to sufficient precision.
*/

public static BigDecimal computePi(int digits)


{
int scale = digits + 5;
BigDecimal arctan1_5 = arctan(5, scale);
BigDecimal arctan1_239 = arctan(239, scale);
BigDecimal pi = arctan1_5.multiply(FOUR).
subtract(arctan1_239).multiply(FOUR);
return pi.setScale(digits, BigDecimal.ROUND_HALF_UP);
}

/*
* Compute the value, in radians, of the arctangent of the inverse of
* the supplied integer to the specified number of digits after the
* decimal point. The value is computed using the power series
* expansion for the arc tangent:
*
* arctan(x) = x-(x^3)/3+(x^5)/5-(x^7)/7+(x^9)/9 ...
*/

public static BigDecimal arctan(int inverseX, int scale)


{
BigDecimal result, numer, term;
BigDecimal invX = BigDecimal.valueOf(inverseX);
BigDecimal invX2 = BigDecimal.valueOf(inverseX * inverseX);
numer = BigDecimal.ONE.divide(invX, scale, roundingMode);
result = numer;
int i = 1;
do
{
numer = numer.divide(invX2, scale, roundingMode);
int denom = 2 * i + 1;
term = numer.divide(BigDecimal.valueOf(denom), scale,
roundingMode);
if ((i % 2) != 0)
result = result.subtract(term);
else
result = result.add(term);
i++;
}
while (term.compareTo(BigDecimal.ZERO) != 0);
return result;
}
}
Deadlock Problems
Deadlock: Thread 1 waits for a resource that thread 2 is holding exclusively and thread 2 is waiting for
a resource that thread 1 is holding exclusively. Neither thread can make progress.

public class DeadlockDemo{


private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void instanceMethod1() {
synchronized(lock1) {
synchronized(lock2) {
System.out.println("first thread in instanceMethod1");
// critical section guarded first by
// lock1 and then by lock2
}
}
}
public void instanceMethod2() {
synchronized(lock2) {
synchronized(lock1) {
System.out.println("second thread in instanceMethod2");
// critical section guarded first by
// lock2 and then by lock1
}
}
}
public static void main(String[] args) {
final DeadlockDemo dld = new DeadlockDemo();
Runnable r1 = new Runnable()
{
@Override
public void run() {
while(true) {
dld.instanceMethod1();
try {
Thread.sleep(50);
}
catch (InterruptedException ie) {
}
}
}
};
Thread thdA = new Thread(r1);
Runnable r2 = new Runnable() {
@Override
public void run() {
while(true){
dld.instanceMethod2();
try{
Thread.sleep(50);
}
catch (InterruptedException ie) {
}
}
}
};
Thread thdB = new Thread(r2);
thdA.start();
thdB.start();
}
}

Volatile and Final Variables


You previously learned that synchronization exhibits two properties: mutual exclusion and visibility.
The synchronized keyword is associated with both properties. Java also provides a weaker form of
synchronization involving visibility only, and associates only this property with the volatile keyword.
Stopping thread
public class ThreadStopping{
public static void main(String[] args){
class StoppableThread extends Thread {
private boolean stopped; // defaults to false
@Override
public void run() {
while(!stopped)
System.out.println("running");
}
void stopThread() {
stopped = true;
}
}
StoppableThread thd = new StoppableThread();
thd.start();
try {
Thread.sleep(1000); // sleep for 1 second
}
catch (InterruptedException ie) {
}
thd.stopThread();
}
}

Attempting to Stop a Thread via the synchronized Keyword

public class ThreadStopping{


public static void main(String[] args) {
class StoppableThread extends Thread {
private boolean stopped; // defaults to false

@Override
public void run(){
synchronized(this) {
while(!stopped)
System.out.println("running");
}
}

synchronized void stopThread() {


stopped = true;
}
}
StoppableThread thd = new StoppableThread();
thd.start();
try {
Thread.sleep(1000); // sleep for 1 second
}
catch (InterruptedException ie) {
}
thd.stopThread();
}
}

public class ThreadStopping{


public static void main(String[] args) {
class StoppableThread extends Thread {
private boolean stopped; // defaults to false
@Override
public void run() {
boolean _stopped = false;
while (!_stopped) {
synchronized(this) {
_stopped = stopped;
}
System.out.println("running");
}
}

synchronized void stopThread(){


stopped = true;
}
}
StoppableThread thd = new StoppableThread();
thd.start();
try{
Thread.sleep(1000); // sleep for 1 second
}
catch (InterruptedException ie){
}
thd.stopThread();
}
}

Attempting to Stop a Thread via the volatile Keyword

public class ThreadStopping{


public static void main(String[] args) {
class StoppableThread extends Thread {
private volatile boolean stopped; // defaults to false

@Override
public void run() {
while(!stopped)
System.out.println("running");
}
void stopThread(){
stopped = true;
}
}
StoppableThread thd = new StoppableThread();
thd.start();
try {
Thread.sleep(1000); // sleep for 1 second
}
catch (InterruptedException ie) {
}
thd.stopThread();
}
}

Creating an Immutable and Thread-Safe Class with Help from final

import java.util.Set;
import java.util.TreeSet;
public final class Planets {
private final Set<String> planets = new TreeSet<>();
public Planets() {
planets.add("Mercury");
planets.add("Venus");
planets.add("Earth");
planets.add("Mars");
planets.add("Jupiter");
planets.add("Saturn");
planets.add("Uranus");
planets.add("Neptune");
}
public boolean isPlanet(String planetName) {
return planets.contains(planetName);
}
}

public class UsePlanets{


public static void main(String[] args){
Planets planets = new Planets();
System.out.println(planets.isPlanet("Earth"));
System.out.println(planets.isPlanet("Vulcan"));
}
}

You might also like