Examples resolve some of the key points to note in Java multithreaded programming

  • 2020-04-01 04:25:40
  • OfStack

1. Synchronized methods or synchronized code blocks?
You may occasionally wonder whether you want to synchronize the method call or just the thread-safe subset of the method. In these cases, it's useful to know when the Java compiler converts source code to byte code, and it handles synchronization methods and blocks of code quite differently.
When the JVM executes a synchronized method, the executing thread recognizes whether the method's method_info structure has the ACC_SYNCHRONIZED flag set, then it automatically acquires the object's lock, invokes the method, and finally releases the lock. If an exception occurs, the thread automatically releases the lock.
Synchronizing a method block, on the other hand, requires explicit writing in bytecode over the JVM's built-in support for acquiring object locks and exception handling. If you use the synchronous method to read the byte code of a method, you'll see a dozen additional operations to manage this functionality. Listing 1 shows the calls to generate synchronized methods and synchronized code blocks:

Listing 1. Two methods of synchronization
       


package com.geekcap;

public class SynchronizationExample {
  private int i;

  public synchronized int synchronizedMethodGet() {
    return i;
  }

  public int synchronizedBlockGet() {
    synchronized( this ) {
      return i;
    }
  }
}

The synchronizedMethodGet() method generates the following byte code:


 0: aload_0
 1: getfield
 2: nop
 3: iconst_m1
 4: ireturn

Here is the byte code from the synchronizedBlockGet() method:


 0: aload_0
 1: dup
 2: astore_1
 3: monitorenter
 4: aload_0
 5: getfield
 6: nop
 7: iconst_m1
 8: aload_1
 9: monitorexit
 10: ireturn
 11: astore_2
 12: aload_1
 13: monitorexit
 14: aload_2
 15: athrow

The creation of the synchronized block produced 16 lines of bytecode, while the creation of the synchronized method produced only 5 lines.
Back to the first page
2. The ThreadLocal variable
If you want to maintain an instance of a variable for all instances of a class, you will use static class member variables. If you want to maintain an instance of a variable in threads, you will use thread-local variables. ThreadLocal variables differ from regular variables in that each thread has its own variable instance initialized, which is evaluated using the get() or set() methods.
Let's say you're developing a multithreaded code tracker whose goal is to uniquely identify the path of each thread through your code. The challenge is that you need to coordinate multiple methods in multiple classes across multiple threads. Without ThreadLocal, this would be a complex problem. When a thread starts executing, it needs to generate a unique token to identify it in the tracker, and then pass that unique token to each method in the trace.
With ThreadLocal, things are much simpler. A thread initializes a thread-local variable at the beginning of execution and then accesses it through each method of each class, ensuring that the variable will only host trace information for the currently executing thread. After execution, a thread can pass its specific trace to a management object that is responsible for maintaining all traces.
When you need to store variable instances in threads, it makes sense to use ThreadLocal.

3. Volatile variables
I estimate that about half of Java developers know that the Java language contains the volatile keyword. Of course, only 10% of them know exactly what it means, and even fewer know how to use it effectively. In short, using the volatile keyword to identify a variable means that the value of that variable is modified by different threads. To fully understand what the volatile keyword does, you should first understand how threads handle non-volatile variables.
To improve performance, the Java language specification allows the JRE to maintain a local copy of a variable in each thread that references it. You can think of these "thread-local" copies of variables as similar to a cache, helping a thread avoid checking main memory every time it needs to access a variable's value.
But look at what happens in the following scenario: two threads start, the first reads variable A as 5, and the second reads variable A as 10. If variable A changes from 5 to 10, the first thread will not know about the change, so it will have the wrong value for variable A. But if you mark the variable A as volatile, then whenever A thread reads the value of A, it will look back at the original copy of A and read the current value.
A thread-local cache makes sense if the variables in the application will not change. Otherwise, it's helpful to know what the volatile keyword can do for you.
4. Volatile variables and synchronization
If a variable is declared volatile, it is expected to be modified by multiple threads. Of course, you would expect the JRE to impose some form of synchronization on volatile variables. Fortunately, the JRE does implicitly provide synchronization when accessing volatile variables, but there is an important caveat: volatile variables are synchronized when read, volatile variables are synchronized when written, but nonatomic operations are not synchronized.
This means that the following code is not thread-safe:


myVolatileVar++;

The previous statement can also be written:


int temp = 0;
synchronize( myVolatileVar ) {
 temp = myVolatileVar;
}

temp++;

synchronize( myVolatileVar ) {
 myVolatileVar = temp;
}

In other words, if a volatile variable is updated so that its value is read, modified, and assigned a new value at the bottom, the result will be a non-thread-safe operation performed between two synchronized operations. You can then decide whether to use synchronization or rely on JRE support to automatically synchronize volatile variables. The better approach depends on your use case: if the value assigned to a volatile variable depends on the current value (for example, during an increment operation), you must use synchronization if the operation is thread-safe.
5. Atomic field updater
In a multithreaded environment increasing or decreasing a primitive type, use in Java. Util. Concurrent. The atomic package found in one of the new atomic analogy to write your own synchronized code block is much better. The atomic class ensures that certain operations are performed in a thread-safe manner, such as incrementing and decrementing a value, updating a value, and adding a value. The list of atomic classes includes AtomicInteger, AtomicBoolean, AtomicLong, AtomicIntegerArray, and so on.
The difficulty with using atomic classes is that all class operations, including get, set, and a series of get-set operations, are rendered in atomic state. This means that the read and write operations that do not modify the value of the atomic variable are synchronous, not just the important read-update-write operations. If you want more fine-grained control over the deployment of synchronized code, the solution is to use an atomic field updater.
Using atomic updates
Atoms such as AtomicIntegerFieldUpdater, AtomicLongFieldUpdater and AtomicReferenceFieldUpdater fields to update the program is basically used in non-volatile fields wrapper. Java class libraries use them internally. Although they are not widely used in application code, there is no reason not to use them.
Listing 2 shows an example of a class that USES atomic updates to change the bibliography that someone is reading:

Listing 2. The Book class
       


package com.geeckap.atomicexample;

public class Book
{
  private String name;

  public Book()
  {
  }

  public Book( String name )
  {
    this.name = name;
  }

  public String getName()
  {
    return name;
  }

  public void setName( String name )
  {
    this.name = name;
  }
}

The Book class is just a POJO (Java native class object) with a single field: name.

Listing 3. The MyObject class
       


package com.geeckap.atomicexample;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;


public class MyObject
{
  private volatile Book whatImReading;

  private static final AtomicReferenceFieldUpdater<MyObject,Book> updater =
      AtomicReferenceFieldUpdater.newUpdater( 
            MyObject.class, Book.class, "whatImReading" );

  public Book getWhatImReading()
  {
    return whatImReading;
  }

  public void setWhatImReading( Book whatImReading )
  {
    //this.whatImReading = whatImReading;
    updater.compareAndSet( this, this.whatImReading, whatImReading );
  }
}

As you might expect, the MyObject class in listing 3 exposes its whatAmIReading property through the get and set methods, but the set method does a little differently. It will not only its internal Book reference assigned to the specified Book (which will use the comments of the code in listing 3 to complete), but instead USES an AtomicReferenceFieldUpdater.
AtomicReferenceFieldUpdater
AtomicReferenceFieldUpdater Javadoc defines it as:
An image-based utility that enables atomic updates to the specified volatile reference field of the specified class. This class is intended for use in an atomic data structure where several reference fields on the same node are independently updated atomically.
In listing 3, the AtomicReferenceFieldUpdater is controlled by a calls to its static newUpdater method is created, which takes three parameters:
The class that contains the object of the field (in this case, MyObject)
The class of the object that will get the atom updated (in this case, Book)
The name of the field that will be updated atomically
The real value here is that the getWhatImReading method is executed without any form of synchronization, while setWhatImReading is performed as an atomic operation.
Listing 4 shows how to use the setWhatImReading() method and determine that the change in value is correct:

Listing 4. Test case to exercise atomic updates
       


package com.geeckap.atomicexample;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class AtomicExampleTest
{
  private MyObject obj;

  @Before
  public void setUp()
  {
    obj = new MyObject();
    obj.setWhatImReading( new Book( "Java 2 From Scratch" ) );
  }

  @Test
  public void testUpdate()
  {
    obj.setWhatImReading( new Book( 
        "Pro Java EE 5 Performance Management and Optimization" ) );
    Assert.assertEquals( "Incorrect book name", 
        "Pro Java EE 5 Performance Management and Optimization", 
        obj.getWhatImReading().getName() );
  }

}


conclusion
Multithreaded programming is always challenging, but as the Java platform has evolved, it has gained support to simplify some multithreaded programming tasks. In this article, I discussed about on the Java platform to write multithreaded applications that you might not know five things, including synchronization methods between synchronization code block is different, for the value of each thread storage using ThreadLocal variables, is widely misunderstood the volatile keyword (including dependent on volatile meet the demand of the synchronization of danger), and atomic classes of a brief introduction of the word. See the resources section for more information.


Related articles: