Java Concurrency BUG Basics

Use thread-safe objects

Shared Objects

The main thread to communicate by sharing access to the same object. Therefore, when the object is read changes may produce unexpected results. Also, while it may make changes to the object in an inconsistent state.

The main methods to avoid concurrency issues such write reliable code is to use immutable objects because they can not be modified by the state of multithreading interference.

But we can not always use immutable objects. In these cases, we must find the variable objects to be thread-safe method.

Collections thread-safe

Like any other object, internally maintains a collection of state. This is accomplished by multiple threads simultaneously change the set of changes. Therefore, we can in a multithreaded environment, a method for the safe use of the collection is synchronized them:

Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
List<Integer> list = Collections.synchronizedList(new ArrayList<>());复制代码

In general, the synchronization will help us achieve mutually exclusive. More specifically, these collections can only be accessed by a thread. Therefore, we can avoid the collection in an inconsistent state.

Multithreading collection

Let us now consider a scenario, we need more to read rather than write. By using a set of synchronization, the application may therefore result in performance degradation. If two threads to read collections simultaneously, a thread must wait for another thread to complete.

Therefore, Java provides concurrent collections, for example CopyOnWriteArrayList, and ConcurrentHashMapcan access them simultaneously by multiple threads:

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
Map<String, String> map = new ConcurrentHashMap<>();复制代码

Above CopyOnWriteArrayListby providing such as adding or deleting a variable operation, create a separate copy of the underlying implementation thread safe. Despite its write performance is worse than Collections.synchronizedList, but when we need to read than write a long time, it provides better performance for us.

ConcurrentHashMapIt is fundamentally thread-safe and non-thread-safe than around Mapthe Collections.synchronizedMaphigher package performance. It is truly thread-safe map implementation class, allowing his son mapping simultaneously different operations.

Use non-thread-safe type

We often use such as SimpleDateFormatbuilt-in objects like objects to parse and format dates. SimpleDateFormatClass in the implementation of actions change its internal state.

We need to be very careful, because they are not thread-safe. Due to race conditions, their state may become inconsistent in multi-threaded applications, resulting in the occurrence of BUG.

So, how to safely use SimpleDateFormat? We have several options:

  • Each time you create a new instance of the use SimpleDateFormat
  • By using a limit on the number of objects created object. It ensures that each thread has its own instance of SimpleDateFormatThreadLocal
  • Concurrent access by multiple threads to synchronize with synced keywords or lock

SimpleDateFormatOnly one example of them. We can use these techniques for any non-thread-safe type.

Competitive conditions

When two or more threads access to shared data and they are trying to change them at the same time, a race condition occurs. Therefore, a race condition may cause run-time error or unexpected results.

Examples of conditions of competition

Let us consider the following code:

class Counter {
    private int counter = 0;
 
    public void increment() {
        counter++;
    }
 
    public int getValue() {
        return counter;
    }
}
复制代码

The class is designed so that the counter increments each method call 1 increases. However, if you reference a Counter object from multiple threads, the interference between threads may disrupt this event as expected. Look, i ++ really unsafe

We can counter ++statement broken down into three steps:

  • Retrieves the current value of the counter
  • The retrieved value is increased by one
  • The increase in value back counter

Now, let us assume that two threads, thread 1 and thread 2, call the incremental approach at the same time. They staggered action may follow in the following order:

  • thread1 read the current value of the counter; 0
  • thread2 read the current value of the counter; 0
  • thread1 increase retrieved values; result 1
  • thread2 increase retrieved values; result 1
  • thread1 stores the result in the counter; the result is now 1
  • thread2 stores the result in the counter; the result is now 1

We expect the counter value is 2, but is 1.

Based synchronization solution

We can resolve inconsistencies by synchronizing key code:

class SynchronizedCounter {
    private int counter = 0;
 
    public synchronized void increment() {
        counter++;
    }
 
    public synchronized int getValue() {
        return counter;
    }
}
复制代码

Any time a thread is only allowed to use the synchronization method of the object, so that the force of the counter read consistency. But the program is also problematic, no matter what the process will have to acquire the lock and release the lock, degrade performance.

solution

We can replace the code above the built-in AtomicIntegerobject. This class provides methods other atoms except for an increase integer, is a better solution than writing your own code. Therefore, we can call its methods without the need for synchronization:

AtomicInteger atomicInteger = new AtomicInteger(3);
atomicInteger.incrementAndGet();复制代码

In this case, SDKyou can solve the problem for us. Otherwise, we may have to write your own code, the key part of the package in the custom thread-safe class. This approach helps us to minimize the complexity of the code and to maximize code reusability.

Collections competitive conditions

problem

Another trap we can fall into is that the protection provided by a set of synchronization is completely reliable.

Let us read the following code:

List<String> list = Collections.synchronizedList(new ArrayList<>());
if(!list.contains("FunTester")) {
    list.add("FunTester");
}复制代码

We list each operation are synchronized, but any combination of multiple method calls are not synchronized. More specifically, in operation between the two, the other thread can modify our set, resulting in poor results.

For example, two threads can simultaneously enter ifthe block, and then update the list, each thread to FunTesterthe value added to the list.

solution

We can use the time synchronization protection code to avoid being accessed by multiple threads:

synchronized (list) {
    if (!list.contains("FunTester")) {
        list.add("FunTester");
    }
}复制代码

We did not add in the function synchronizedkey, and instead created a listkey part related to the part allowing only one thread to perform this operation. We can use a list of objects to other operations synchronized (list), to ensure that only one thread can perform any operation on this object.

ConcurrentHashMapThe built-in solution

In ConcurrentHashMapprovides this type of problem a better solution. We can use their atoms putIfAbsentmethods:

Map<String, String> map = new ConcurrentHashMap<>();
map.putIfAbsent("foo", "bar");复制代码

Or, if we want to calculate the value, the atoms of which computeIfAbsentmethod:

map.computeIfAbsent("foo", key -> key + "bar");

We should note that these methods are Mappart of the interface, which provides a convenient way to avoid writing about the insertion of conditional logic. When multiple threads try to call, they can really help us.

  • Solemnly declare : The article first appeared in public No. "FunTester", prohibit third parties (except Tencent cloud) reproduce, publish.

Technology Featured articles

Non-technical Selected Articles

Guess you like

Origin juejin.im/post/5e49f8c8e51d45271e2a6a0e