This is the 19th day of my participation in the August More Text Challenge

Multithreading has been the difficulty in Java development, but also in the interview of frequent, while there is still time, I intend to consolidate the JUC knowledge, I think the opportunity is everywhere, but is always left to prepare for the people, I hope we can come on!!

Go down, come up again, I think we’ll be different.

JUC series

  • What is JUC?
  • JUC series (ii) Review Synchronized keyword
  • JUC series (three) Lock Lock mechanism detail code theory combined
  • The thread safety problem of JUC series (4)
  • JUC series (5) | Synchonized keywords to further explain
  • JUC series (6) | Callable and interface explanation & use, FutureTask application Future
  • JUC series (7) | JUC three commonly used tools CountDownLatch, CyclicBarrier, Semaphore

Is being continuously updated…

I’m sure we’ve all used ArrayList. I don’t know if you’ve ever thought about it in terms of thread safety.

I. Introduction of problems:

Let’s take a look at the following program first, and see if you can see any problems?

public static void main(String[] args) {
    List list = new ArrayList();
    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            list.add(UUID.randomUUID().toString());
            System.out.println(list);
        }, "Thread"+ i).start(); }}Copy the code

Do you think it will work every time?

The answer is no, it may run the program several times and it won’t go wrong, but every once in a while it will. Will quote a ConcurrentModificationException exception, English named: concurrent modification abnormalities.

Reason: because we are reading, we also encounter write operation, we do not have synchronization code block, lock, so it is definitely not able to continue to execute.

And the add method of ArrayList is not thread synchronous. (jdk11 source)

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}
Copy the code

How can we solve this problem??

Second, solutions

The first way: use Vector

We can use Vector instead of ArrayList because Vector inherits AbstractList and implements the List, RandmoAccess interface.

RandmoAccess is a Java implementation used by lists to provide fast access to lists. In a Vector, we can get an element object quickly by its ordinal number; This is a quick random interview.

public class Vector<E> extends AbstractList<E> implements List<E>,RandomAccess 
Copy the code

After we modify the above program, the program will no longer appear abnormal.

public static void main(String[] args) {
    List list = new Vector();
    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            list.add(UUID.randomUUID().toString());
            System.out.println(list);
        }, "Thread"+ i).start(); }}Copy the code

The reason lies in the Vector code.

public synchronized boolean add(E e) {
    modCount++;
    add(e, elementData, elementCount);
    return true;
}
Copy the code

Adding the synchronized keyword to the add method makes it a synchronized method block.

The second way: use Collections

Collections provides the synchronizedList method to ensure that lists are thread-safe for synchronization.

Collections contain only static methods that operate on or return Collections, so we often refer to Collections as a utility class for Collections.

public static void main(String[] args) {
    List list = Collections.synchronizedList(new ArrayList<>());
    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            list.add(UUID.randomUUID().toString());
            System.out.println(list);
        }, "Thread"+ i).start(); }}Copy the code

No exceptions will occur. The source code is also reflected

public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}
Copy the code

Most methods provide both synchronous and unsynchronized apis.

Third way: Use CopyOnWriteArrayList

CopyOnWriteArrayList, like ArrayList, is a mutable array.

It has the following characteristics:

  1. Update operations are expensive (add(), set(), remove(), and so on) because you copy the entire array
  2. Thread-safe.
  3. It is best suited for applications where the List size is typically kept small, read-only operations are far more common than mutable operations, and you need to prevent collisions between threads during traversal.
  4. Inefficient exclusive lock: use the read-write separation idea
  5. Writer thread acquires lock, other writer thread blocks
  6. Copy the ideas

Ideas and principles of CopyOnWriteArrayList:

When we want to add an element, we do not add it directly to the current container. Instead, we should first make a copy of the current container, and then add it to the new container. After the addition is complete, we can make the reference of the original container to the new container.

Of course, this throws up a new problem, namely the problem of inconsistent data. If the writer thread does not have time to write to memory, other threads will read the dirty data.

public static void main(String[] args) {
    List list = new CopyOnWriteArrayList();
    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            list.add(UUID.randomUUID().toString());
            System.out.println(list);
        }, "Thread"+ i).start(); }}Copy the code

Why isn’t there a thread safety issue?

Let’s look at it from both “dynamic arrays” and “thread-safe” perspectives:

Dynamic array mechanism:

  • It has a volatile array inside to hold data.

  • It creates new arrays when it comes to updates, so CopyOnWriteArrayList is inefficient; But if you just do the traversal lookup, you can achieve higher efficiency.

    public boolean add(E element) {
        synchronized (lock) {
            checkForComodification();
            CopyOnWriteArrayList.this.add(offset + size, element);
            expectedArray = getArray();
            size++;
        }
        return true;
    }
    // CopyOnWriteArrayList.this.add(offset + size, element);
    public void add(int index, E element) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException(outOfBounds(index, len));
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(es, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(es, 0, newElements, 0, index);
                System.arraycopy(es, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); }}Copy the code

Thread safety mechanism:

  • This is achieved through volatile and synchronized.

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;
    Copy the code
  • Volatile arrays are used to store data

    • When a thread reads a volatile array, it can always see the last write to that volatile variable by another thread. In this way, volatile provides the guarantee that the data read is always up to date.
  • Data is protected by mutex

    • During update operations, the mutexes are acquired first, and after modification, the data is updated to a volatile array, and then the mutexes are released to keep the data safe.

In addition to ArrayList, which is not thread safe, HashMap and HashSet are not thread safe. HashMap, HashSet, Hashtable, CopyOnWriteArraySet, ConcurrentHashMap

Three, talk to yourself

Recently, I started to learn JUC again. I feel there is a lot of Java content, but I still think I need to lay a solid foundation in order to go further.

Recently in the continuous update, if you feel helpful to you, also interested in the words, pay attention to me, let us study together, discuss together.

Hello, I am ning Zhichun, a blogger, a small seed on the way of Learning Java. I also hope that one day I can take root and grow into a tree in the sky.

Hope to share with you 😁

By the time we meet again, we have achieved something.