Data Types

We have seen two ways to spawn a thread, using the Thread and AsyncTask classes. When two or more threads access the same data, you need to make sure the data types support concurrent access. The Java language defines many classes in the java.util.concurrent package for that purpose:

  • ArrayBlockingQueue
  • ConcurrentHashMap
  • ConcurrentLinkedQueue
  • ConcurrentSkipListMap
  • ConcurrentSkipListSet
  • CopyOnWriteArrayList
  • CopyOnWriteArraySet
  • DelayQueue
  • LinkedBlockingDeque
  • LinkedBlockingQueue
  • PriorityBlockingQueue
  • SynchronousQueue

You will have to carefully select your data types based on your application's requirements. Also, the fact that they are concurrent implementations does not necessarily imply that operations are atomic. In fact, many operations are not atomic and by design are not meant to be. For example, the putAll() method in the ConcurrentSkipListMap class is not atomic. A concurrent implementation merely means that the data structure will not be corrupted when accessed by multiple threads.

Synchronized, Volatile, Memory Model

If you want to share objects between multiple threads but have not implemented any fine-grained locking mechanism, you can use the synchronized keyword to make sure your access is thread-safe, as shown in Listing 5–10.

Listing 5–10. Using the Synchronized Keyword

public class MyClass {
    private int mValue;

    public MyClass(int n) {
        mValue = n;
    }

    public synchronized void add (int a) {

        mValue += a;
    }

    public synchronized void multiplyAndAdd (int m, int a) {
        mValue = mValue * m + a;
    }
}

The two methods add and multiplyAndAdd in Listing 5–7 are synchronized methods. This means two things:

  • If one thread is executing a synchronized method, other threads trying to call any synchronized method for the same object have to wait until the first thread is done.
  • When a synchronized method exits, the updated state of the object is visible to all other threads.

The first item is quite intuitive. The second one should be as well although it still requires an explanation. As a matter of fact, the Java memory model is such that a modification to a variable in one thread may not immediately be visible to other threads. Actually, it may never be visible. Consider the code in Listing 5–11: if one thread calls MyClass.loop(), and at some point in the future another thread calls Myclass.setValue(100), the first thread may still not terminate; may carry on looping forever and always print out a value other than 100, simply because of the Java language's memory model.

Listing 5–11. Java Memory Model Impact

public class MyClass {
    private static final String TAG = "MyClass";
    private static int mValue = 0;

    public static void setValue(int n) {
        mValue = n;
    }

    public static void loop () {
        while (mValue != 100) {
            try {
                Log.i(TAG, “Value is ” + mValue);
                Thread.sleep(1000);
            } catch (Exception e) {
                // ignored
            }
        }
    }
}

You have two options to fix that:

Listing 5–12. Adding the Synchronized Keyword

public class MyClass {
    private static final String TAG = "MyClass";
    private static int mValue = 0;

    public static synchronized void setValue(int n) {
        mValue = n;
    }

    public static synchronized int getValue() {
        return mValue;
    }

    public static void loop () {
        int value;
        while ((value = getValue()) != 100) {
            try {
                Log.i(TAG, “Value is ” + value);
                Thread.sleep(1000);
            } catch (Exception e) {
                // ignored
            }
        }
    }
}

Listing 5–13. Adding the Volatile Keyword

public class MyClass {
    private static final String TAG = "MyClass";
    private static volatile int mValue = 0; // we add the volatile keyword and remove
the synchronize keyword

    public static void setValue(int n) {
        mValue = n; // you'd still have to use synchronized if that statement were
mValue += n (not atomic)
    }

    public static void loop () {
        while (mValue != 100) {
            try {
                Log.i(TAG, “Value is ” + mValue);
                Thread.sleep(1000);
            } catch (Exception e) {
                // ignored
            }
        }
    }
}

NOTE: Make sure you understand which statements are atomic. For example, value++ is not atomic while value = 1 is. This is important as the volatile keyword can only fix concurrency issues if the statements using the variable are atomic. If they are not, then you have to use the synchronized keyword instead.

You can improve concurrency and throughput by using synchronized statements, as shown in Listing 5–14, as opposed to making whole methods synchronized. In these cases, you want to protect only the part that needs to be protected (that is, where mValue is being modified), but leave the log message outside of the synchronized block. You can also use objects other than this as a lock.

Listing 5–14. Synchronized Statements

public class MyOtherClass {
    private static final String TAG = "MyOtherClass";
    private int mValue;
    private Object myLock = new Object(); // more than this… there is

    public MyClass(int n) {
        mValue = n;
    }

    public void add (int a) {
        synchronized (myLock) {
            mValue += a;
        }
        Log.i(TAG, "add: a=" + a); // no need to block
    }

    public void multiplyAndAdd (int m, int a) {
        synchronized (myLock) {
            mValue = mValue * m + a;
        }
        Log.i(TAG, " multiplyAndAdd: m=" + m + ", a=" + a); // no need to block
    }
}

Making methods or statements synchronized is the easiest way to guarantee your class supports concurrent access. However, it may reduce the throughput when not everything needs to be protected, and even worse, it can cause deadlocks. Indeed, deadlocks can occur when you call another object's method from within a synchronized block, which may attempt to acquire a lock on an object that is already locked and waiting for your own object's lock.

TIP: Don't call another object's method within a synchronized block unless you can guarantee no deadlock will occur. Usually, you can guarantee that only when you are the author of the code of the other object's class.

In general, it is best to avoid accessing the same object from different threads if you have any doubt about whether it will work or not. The classes defined by Java are a good reference, and since the Android code is available, you can refer to the implementation of these classes to understand the various techniques you can take advantage of. In addition, to simplify your development, Java defines many classes that already support thread-safe or concurrent programming, which can be used as the basis for some of your own algorithms.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.74.18