Difference between revisions of "CSC352: Java Threads and Synchronization Examples"

From dftwiki3
Jump to: navigation, search
(A second way of synchronizing the threaded computation of Pi)
Line 5: Line 5:
 
<br />
 
<br />
 
<source lang="java">
 
<source lang="java">
 +
/*
 +
* UnsynchronizedThreadExample.java
 +
* D. Thiebaut
 +
* Undocumented code that computes Pi with 2 threads, but is terribly
 +
* flawed in the way it updates the global sum...
 +
*/
 
package DT;
 
package DT;
  
 
public class UnsynchronizedThreadExample {
 
public class UnsynchronizedThreadExample {
  
static int sum = 0;
+
        static int sum = 0;
+
       
class PiThreadBad extends Thread {
+
        class PiThreadBad extends Thread {
private int N; // the total number of samples/iterations  
+
                private int N;                 // the total number of samples/iterations  
  
public PiThreadBad( int Id, int N ) {
+
                public PiThreadBad( int Id, int N ) {
super( "Thread-"+Id ); // give a name to the thread
+
                        super( "Thread-"+Id ); // give a name to the thread
this.N = N;
+
                        this.N         = N;
}
+
                }
+
                       
@Override
+
                @Override
public void run() {
+
                public void run() {
for ( int i=0; i<N; i++ )
+
                        for ( int i=0; i<N; i++ )
sum ++;
+
                                sum ++;
}
+
                }
}
+
        }
  
public void process( int N ) {
+
        public void process( int N ) {
PiThreadBad t1 = new PiThreadBad( 0, N );
+
            long startTime = System.currentTimeMillis();
PiThreadBad t2 = new PiThreadBad( 1, N );
+
            PiThreadBad t1 = new PiThreadBad( 0, N );
+
            PiThreadBad t2 = new PiThreadBad( 1, N );
//--- start two threads ---
+
               
t1.start();
+
            //--- start two threads ---
t2.start();
+
            t1.start();
+
            t2.start();
//--- wait till they finish ---
+
               
try {
+
            //--- wait till they finish ---
t1.join();
+
            try {
t2.join();
+
                t1.join();
} catch (InterruptedException e) {
+
                t2.join();
e.printStackTrace();
+
            } catch (InterruptedException e) {
}
+
                e.printStackTrace();
+
            }
System.out.println( "sum = " + sum );
+
               
}
+
            System.out.println( "sum = " + sum );
+
            System.out.println( "Execution time: " + (System.currentTimeMillis()-startTime) + " ms" );
public static void main(String[] args) {
+
        }
int N = 100000000;
+
   
UnsynchronizedThreadExample U = new UnsynchronizedThreadExample();
+
    public static void main(String[] args) {
U.process( N );
+
        int N = 100000000;
}
+
        UnsynchronizedThreadExample U = new UnsynchronizedThreadExample();
 +
        U.process( N );
 +
    }
  
 
}
 
}

Revision as of 10:45, 5 September 2013

--D. Thiebaut (talk) 21:12, 4 September 2013 (EDT)


A Badly Written (and Flawed) Multithreaded Computation of Pi


/*
 * UnsynchronizedThreadExample.java
 * D. Thiebaut
 * Undocumented code that computes Pi with 2 threads, but is terribly
 * flawed in the way it updates the global sum...
 */
package DT;

public class UnsynchronizedThreadExample {

        static int sum = 0;
        
        class PiThreadBad extends Thread {
                private int N;                  // the total number of samples/iterations 

                public PiThreadBad( int Id, int N ) {
                        super( "Thread-"+Id ); // give a name to the thread
                        this.N          = N;
                }
                        
                @Override
                public void run() {
                        for ( int i=0; i<N; i++ )
                                sum ++;
                }
        }

        public void process( int N ) {
            long startTime = System.currentTimeMillis();
            PiThreadBad t1 = new PiThreadBad( 0, N );
            PiThreadBad t2 = new PiThreadBad( 1, N );
                
            //--- start two threads ---
            t1.start();
            t2.start();
                
            //--- wait till they finish ---
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                
            System.out.println( "sum = " + sum );
            System.out.println( "Execution time: " + (System.currentTimeMillis()-startTime) + " ms" );
        }
    
    public static void main(String[] args) {
        int N = 100000000;
        UnsynchronizedThreadExample U = new UnsynchronizedThreadExample();
        U.process( N );
    }

}


Output

sum = 180612836
Execution time: 19 ms

Note that the sum should really be 200000000, as both threads increment sum 100000000 times. The result is certainly incorrect.

Note also that the execution time is quite fast: 19 ms.


A Synchronized Version of the Same Program

We decide to put the statement that increments the variable sum into a function, and ask Java to synchronize around the function, i.e. make sure than only one thread at a time runs through this function. In other word, the synchronized function becomes atomic for threads.


package DT;

public class SynchronizedThreadExample {

	int sum = 0;
	Integer lock=0;
	
	SynchronizedThreadExample() {
		sum = 0;
	}
	
	class PiThreadBad extends Thread {
		private int N;			// the total number of samples/iterations 

		public PiThreadBad( int Id, int N ) {
			super( "Thread-"+Id ); // give a name to the thread
			this.N 		= N;
		}
			
		@Override
		public void run() {
			for ( int i=0; i<N; i++ )
				synchronized( lock ) {
					sum++;
				}
		}
	}
	
	
	public void process( int N ) {
		long startTime = System.currentTimeMillis();

		PiThreadBad t1 = new PiThreadBad( 0, N );
		PiThreadBad t2 = new PiThreadBad( 1, N );
		
		//--- start two threads ---
		t1.start();
		t2.start();
		
		//--- wait till they finish ---
		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println( "sum = " + sum );
		System.out.println( "Execution time: " + (System.currentTimeMillis()-startTime) + " ms" );
	}
	
	public static void main(String[] args) {
		int N = 100000000;
		SynchronizedThreadExample U = new SynchronizedThreadExample();
		U.process( N );
	}

}


Output

sum = 200000000
Execution time: 8448 ms

Note that the result is now correct. However the execution time is 400 longer!


A second way of synchronizing the threaded computation of Pi


This time, instead of creating a synchronized method (by the way, the synchronized method should not be one of the thread's method, but a method outside the inherited thread class), we synchronize on an object global to the threads and the main program. This object cannot be a simple type (such as int), but a real object (e.g. Integer).

package DT;

public class SynchronizedThreadExample2 {

	static int sum = 0;
	
	class PiThreadBad extends Thread {
		private int N;			// the total number of samples/iterations 

		public PiThreadBad( int Id, int N ) {
			super( "Thread-"+Id ); // give a name to the thread
			this.N 		= N;
		}
			
		@Override
		public void run() {
			for ( int i=0; i<N; i++ )
				incrementSum();
		}
	}
	
	private synchronized void incrementSum() {
		sum++;
	}
	
	public void process( int N ) {
		long startTime = System.currentTimeMillis();

		PiThreadBad t1 = new PiThreadBad( 0, N );
		PiThreadBad t2 = new PiThreadBad( 1, N );
		
		//--- start two threads ---
		t1.start();
		t2.start();
		
		//--- wait till they finish ---
		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println( "sum = " + sum );
		System.out.println( "Execution time: " + (System.currentTimeMillis()-startTime) + " ms" );

	}
	
	public static void main(String[] args) {
		int N = 100000000;
		SynchronizedThreadExample2 U = new SynchronizedThreadExample2();
		U.process( N );
	}

}


Output

sum = 200000000
Execution time: 8620 ms
 

Similar behavior as the first version. The synchronization code definitely add a serious overhead to the computation. Sometimes it is a necessary solution for a problem. In other cases, such as in the computation of Pi, we can find an approach that is safe but does not require synchronization.