Separation of reading and writing in multithreaded environment

Time:2022-1-5

1、 Scene

When we operate a collection in a multi-threaded environment, such as ArrayList or HashMap, these collections must be thread unsafe by default. If multiple threads read and write these collections at the same time, there will be thread safety problems.

OK, here’s the question. How can we make a collection thread safe?

2、 Synchronized or lock lock lock

There is a very simple way to add thread synchronization control, or lock, to the access to these collections.

Here you can go to another article to see the principle of synchronized and lock locks and briefly describe synchronized and lock locks

The simplest way is to add a synchronized or lock lock lock.

We assume that we use readwritelock to control access to these collections.

In this way, multiple read requests can be executed simultaneously to read data from these sets, but read requests and write requests are mutually exclusive, and write requests and write requests are mutually exclusive.

public Object  read() {

   lock.readLock().lock();

//Read operation on collection

   lock.readLock().unlock();

}

public void write() {

   lock.writeLock().lock();

//Write to collection

   lock.writeLock().unlock();

}

Let’s think about it. What’s wrong with the code like the above?

The biggest problem is that write locks and read locks are mutually exclusive. It is assumed that the write operation frequency is very low and the read operation frequency is very high, which is a scenario of less writing and more reading.

Then, when a write operation is occasionally performed, will the write lock be added? Will a large number of read operations be blocked and unable to be performed?Big data training

This is the biggest problem that read-write locks may encounter.

3、 Get inspiration from Kafka source code

Kafka implements a copyonwritemap to solve the above problems. This copyonwritemap adopts the idea of copyonwrite, which is similar to the idea of separation of reading and writing.

Let’s take a look at the source code implementation of this copyonwritemap:

//Typical volatile modifies ordinary map

  private volatile Map map;

  @Override

  public synchronized V put(K k, V v) {

//When updating, first create a copy, update the copy, and then assign a value to the volatile variable and write it back

      Map copy = new HashMap(this.map);

      V prev = copy.put(k, v);

      this.map = Collections.unmodifiableMap(copy);

      return prev;

  }

  @Override

  public V get(Object k) {

//When reading, directly read the map data structure referenced by the volatile variable without locking

      return map.get(k);

  }

If you are a write operation (put), it will first create a copy, and then use a syn lock on the copy, so as to ensure that only one thread can modify the copy at a time, while the read operation (get) is not locked directly, because no matter how many threads read, the log data will not be affected, which can not only ensure the thread safety of reading and writing, but also greatly improve the performance!

The most wonderful thing about him here is how to ensure that what you read is the latest data? He uses volatile to decorate the map. Volatile is to ensure the visibility in a multi-threaded environment. When we update the copy in a write operation, it will immediately update the data we read, thus solving the most difficult data synchronization problem of read-write separation!