简体   繁体   中英

java thread synchronization fails

Java does not appear to be 100% accurate with thread synchronization. The code in this example prints the value of a static integer which is incremented by each thread. Should the output include the same number more than once uniq will identify it. Each of the examples is run by the Makefile script to help illustrate the problem. Each example uses a different method of synchronization/locking, yet none appears to work well 100% of the time. Most of the duplication happens early on in the loop, on this system at least.

Makefile:

JAVA=/usr/local/jdk/bin/java
JAVAC=$(JAVA)c

build:
    $(JAVAC) Synchron.java
    $(JAVAC) SynchronVolatile.java
    $(JAVAC) SynchronFinal.java
    $(JAVAC) SynchronThis.java
    $(JAVAC) SynchronA.java
    $(JAVAC) SynchronObj.java

run:
    $(JAVA) Synchron | sort | uniq -c | egrep -v '^\s+1\s+' ; /bin/true
    $(JAVA) SynchronVolatile | sort | uniq -c | egrep -v '^\s+1\s+' ; /bin/true
    $(JAVA) SynchronFinal | sort | uniq -c | egrep -v '^\s+1\s+' ; /bin/true
    $(JAVA) SynchronThis | sort | uniq -c | egrep -v '^\s+1\s+' ; /bin/true
    $(JAVA) SynchronA | sort | uniq -c | egrep -v '^\s+1\s+' ; /bin/true
    $(JAVA) SynchronObj | sort | uniq -c | egrep -v '^\s+1\s+' ; /bin/true

Synchron.java:

import java.io.*;
import java.util.*;

public class Synchron implements Runnable {
        static int a;

        synchronized public void adder() {
        Synchron.a++;
        System.out.println( Synchron.a );
        }

        public void run() {
                while( Synchron.a < 65535 ) {
                        adder();
                }
        }
        public static void main( String []args ) {
                ArrayList <Thread>al = new ArrayList<Thread>();

                try {
                        int i;
                        for( i = 0; i<10 ; i++ ) {
                                Synchron s = new Synchron();
                                Thread t = new Thread( s );
                                al.add(t);
                                t.start();
                        }

                        for( Thread t : al ) {
                                t.join();
                        }
                }
                catch( Exception e ) {
                        e.printStackTrace();
                }

        }
}

SynchronVolatile.java:

import java.io.*;
import java.util.*;

public class SynchronVolatile implements Runnable {
        static int a;
    static volatile Object o = new Object();

        public void adder() {
        synchronized( SynchronVolatile.o ) {
            SynchronVolatile.a++;
        }
        System.out.println( SynchronVolatile.a );
        }

        public void run() {
                while( SynchronVolatile.a < 65535 ) {
                        adder();
                }
        }
        public static void main( String []args ) {
                ArrayList <Thread>al = new ArrayList<Thread>();

                try {
                        int i;
                        for( i = 0; i<10 ; i++ ) {
                                SynchronVolatile s = new SynchronVolatile();
                                Thread t = new Thread( s );
                                al.add(t);
                                t.start();
                        }

                        for( Thread t : al ) {
                                t.join();
                        }
                }
                catch( Exception e ) {
                        e.printStackTrace();
                }

        }
}

SynchronFinal: This is the same as SynchronVolatile.java, except it uses a final for Object o, rather than volatile.

SynchronThis.java:

import java.io.*;
import java.util.*;

public class SynchronThis implements Runnable {
        static int a;
    static volatile Object o = new Object();

        public void adder() {
        synchronized( this ) {
            SynchronThis.a++;
        }
        System.out.println( SynchronThis.a );
        }

        public void run() {
                while( SynchronThis.a < 65535 ) {
                        adder();
                }
        }
        public static void main( String []args ) {
                ArrayList <Thread>al = new ArrayList<Thread>();

                try {
                        int i;
                        for( i = 0; i<10 ; i++ ) {
                                SynchronThis s = new SynchronThis();
                                Thread t = new Thread( s );
                                al.add(t);
                                t.start();
                        }

                        for( Thread t : al ) {
                                t.join();
                        }
                }
                catch( Exception e ) {
                        e.printStackTrace();
                }

        }
}

SynchronA.java:

import java.io.*;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SynchronA implements Runnable {
        static int a;
    private volatile Lock lock = new ReentrantLock();

        public void adder() {
        lock.lock();
        SynchronA.a++;
        System.out.println( SynchronA.a );
        lock.unlock();
        }

        public void run() {
                while( SynchronA.a < 65535 ) {
                        adder();
                }
        }
        public static void main( String []args ) {
                ArrayList <Thread>al = new ArrayList<Thread>();

                try {
                        int i;
                        for( i = 0; i<10 ; i++ ) {
                                SynchronA s = new SynchronA();
                                Thread t = new Thread( s );
                                al.add(t);
                                t.start();
                        }

                        for( Thread t : al ) {
                                t.join();
                        }
                }
                catch( Exception e ) {
                        e.printStackTrace();
                }

        }
}

SynchronObj.java:

import java.io.*;
import java.util.*;

public class SynchronObj implements Runnable {
        static int a;
    Object o;

    public SynchronObj( Object obj ) {
        o = obj;
    }

        public void adder() {
        synchronized( o ) {
            SynchronObj.a++;
        }
        System.out.println( SynchronObj.a );
        }

        public void run() {
                while( SynchronObj.a < 65535 ) {
                        adder();
                }
        }
        public static void main( String []args ) {
                ArrayList <Thread>al = new ArrayList<Thread>();

        final Object o = new Object();

                try {
                        int i;
                        for( i = 0; i<10 ; i++ ) {
                                SynchronObj s = new SynchronObj( o );
                                Thread t = new Thread( s );
                                al.add(t);
                                t.start();
                        }

                        for( Thread t : al ) {
                                t.join();
                        }
                }
                catch( Exception e ) {
                        e.printStackTrace();
                }

        }
}

When this runs, none of the methods of thread synchronization above work 100% of the time. Any ideas of what could be going wrong?

Your issue is that in some cases your locks are locking on different lock object instances, so they actually never interfere with other.

Change

Object o;

to

public static final Object o = new Object();

Now all your synchronized statements will try to lock on the same object, and the correct lock contention will occur.

Also this looks suspect:

while (SynchronObj.a < 65535) {...}

since you are reading the value of a without synchronization . Definitely an issue.

It also seems that your method of testing synchronization is by searching for duplicate outputs printed. Instead, try doing

    public void run() {
            for (int i=0; i<10000; i++) {
                    adder();
            }
    }

Because you are running 10 threads, simply verify if the final answer is 10000*10 . Anything less/more will imply incorrect thread synchronizatoin.

Martin Konecny has pointed out one major issue with your code. All Threads must content for the same lock Object for proper synchronization. This is why your attempts Synchron , SynchronThis and SynchronObj are not going to work.

Another issue, however, is that your call to

System.out.println(SynchronObj.a)

occurs outside the synchronized block. This will also lead to some problems. Consider following possible execution scenario:

Let us assume the value of "a" is 30. Thread 1 enters method adder() and locks on Object o. It increments "a" and releases the lock. Now a == 31 . Before it prints the value of "a", however, Thread 1 is paused and Thread 2 begins its execution. Thread 2 obtains the lock, increments "a" and releases the lock. Now a == 32 . Thread 2 continues with its execution and calls

System.out.println(SynchronObj.a)

The values printed on screen went from 30 to 32. 31 was "swallowed". So make sure that you also print from within your synchronized block. Always synchronize read and writes of shared variables. Even if you think that it might be not necessary, the compiler might rearrange your code. Read here for more info on this behavior.

You should also avoid the non-synchronized access of "a" in the while loop. One alternative would be:

static int a;
static Object o = new Object();
static boolean cont = true;

public void adder() {
    synchronized( o ) {
        if (cont) {
            MainClass.a++;
            System.out.println( MainClass.a );
            cont = (MainClass.a < 65535);
        }
    }
}

public void run() {
    while(MainClass.cont) {
        adder();
    }
}

Finally, you do not need to declare your lock object as volatile . The volatile keyword tells the JVM to not cache a variables value thread-locally. The current value shall instead be read from memory. This happens automatically inside a synchronized block. More info on volatile also in Chapter 17.4 Memory Model .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM