Thread-safe class definition: there are no race conditions (there are no modified member variables in the class), or synchronization control exists.

Causes of multithreading insecurity – race conditions/critical sections

The same program running in more than one thread itself does not have thread safety problems, the problem is that multiple threads access shared resources, such as: class member variables (common or static variables), system shared resources (files, databases), etc.

At the same time, thread-safety problems only occur when multiple threads write to these resources at the same time, not when resources are not modified.

Public class Counter {// Public class Counter {// Public class Counter {// Public class Counter { Public void add(long value){// There are three operations: 1. Count this.count = this.count + value; }}Copy the code

For example, thread A and thread B execute the add () method of the same Counter instance at the same time. We do not know when the operating system switches between threads. The JVM does not execute this method as A single instruction, but in the following order:

1. Fetch the value of this.count from memory and place it in register 2. Add register value to value 3. Write the result value in the register back to memoryCopy the code

However, when multiple threads execute, they share the count member variable of the same instance, and when scheduled to execute, they may execute in the following order:

A: Read the value of this.count from memory to register B: read the value of this.count from memory to register B: add value=2 B: write the result of register back to memory. At this point, the value of this.count in memory is 2. A: Write the result in the register back to memory, overwriting the original result value to 3, and the execution endsCopy the code

Two threads add two and three to count, respectively, and at the end of each thread’s execution, it should be five, but it’s actually three. When two threads cross, the initial values read are both 0 and written back to 2 or 3, respectively, with the latter overwriting the former. Not synchronizing such multithreaded access can result in thread-unsafe results.

  • Race conditions & critical regions

Race conditions exist when multiple threads access the same resource and are sequence-sensitive. The area of code that causes race conditions to occur is called a critical region.

The add() method in the above example is a critical section that generates race conditions. Race conditions can be avoided by using appropriate synchronization in critical sections.

Class for a shared resource that can cause thread insecurity

Race conditions are raised when multiple threads access a shared resource variable and write to it. Simultaneous reading does not generate race conditions.

  • Local primitive type variables in a method

When a method of a class (including static method and member method) is run simultaneously in multiple threads, the local variable in the method will have a copy in the stack space of each thread, and its modification will not affect other threads, so there is no thread-safety problem, and the member variable will cause thread-safety problems according to different situations.

public void someMethod(){
  long threadSafeInt = 0;
  threadSafeInt++;
}
Copy the code
  • A local object in a method references a variable

Object references exist in the thread stack of each thread, but new object instances are in the shared heap. If local objects created in a method do not escape the method, the class is thread-safe. Even if you pass the object as an argument to another method, it is thread-safe as long as it is not available to another thread.

Escape: That is, the object is not retrieved by other methods and is not referenced by non-local variables.

public void someMethod(){
  LocalObject localObject = new LocalObject();
  localObject.callMethod();
  method2(localObject);
}

public void method2(LocalObject localObject){
  localObject.setValue("value");
}
Copy the code

The LocalObject in the sample is not returned by the method, nor is it passed to an object outside the someMethod() method. Each thread executing someMethod() creates its own LocalObject object and assigns a value to a LocalObject reference. Therefore, the LocalObject here is thread-safe. In fact, the entire someMethod() is thread-safe. Even when LocalObject is passed as a parameter to other methods of the same class or to methods of other classes, it is still thread-safe. Of course, if a LocalObject is passed to another thread in some way, it is no longer thread-safe.

  • Object member variable

Object members are stored on the heap. If two threads update the same member of the same object at the same time, the code is not thread-safe.

public class NotThreadSafe{ StringBuilder builder = new StringBuilder(); public add(String text){ this.builder.append(text); }}Copy the code

If two threads call the add() method on the same NotThreadSafe instance at the same time, there are race conditions.

Notice that both MyRunnable share the same NotThreadSafe object. Therefore, they create race conditions when they call the add() method. NotThreadSafe sharedInstance = new NotThreadSafe(); new Thread(new MyRunnable(sharedInstance)).start(); new Thread(new MyRunnable(sharedInstance)).start(); public class MyRunnable implements Runnable{ NotThreadSafe instance = null; public MyRunnable(NotThreadSafe instance){ this.instance = instance; } public voidrun(){
    this.instance.add("some text"); }}Copy the code
Of course, if the two threads call the call() method on different NotThreadSafe instances, no race conditions will result. Here is a slightly modified example: new Thread(new MyRunnable(new NotThreadSafe())).start(); new Thread(new MyRunnable(new NotThreadSafe())).start();Copy the code

Now that both threads have their own separate NotThreadSafe objects, add() calls will not interfere with each other, and there will be no race conditions. So non-thread-safe objects can still eliminate race conditions in some way.

  • Thread control escape rule

Thread-safe classes: Do not contain race conditions, that is, when there are no shared resource variables or when there are appropriate synchronization controls

Immutable objects are thread-safe

We can achieve thread-safety by creating immutable shared objects that cannot be modified when shared between threads. The following is an example:

Note that the add() method returns the result of the addition operation as a new ImmutableValue class instance rather than operating directly on its own value variable. public class ImmutableValue{ private int value = 0; public ImmutableValue(int value){ this.value = value; } public intgetValue() {return this.value;
    }
    
    public ImmutableValue add(int valueToAdd){
        returnnew ImmutableValue(this.value + valueToAdd); }}Copy the code

Note that the member variable value of the ImmutableValue class is assigned by the constructor, and there is no set method in the class. This means that once an ImmutableValue instance is created, the value variable cannot be modified, which is immutability. But you can read the value of this variable with the getValue() method.

  • Even if an object is a thread-safe immutable object, it may not be thread-safe in another class that contains a reference to that object.