Several thread-safe lists synchronizedList()&CopyOnWriteArrayList of java multithreading

 ArrayList under concurrent

So what exactly will happen to it? Let's write a simple code to take a look:

In this code, we create two threads and add 10,000 elements to the ArrayList at the same time. If we run this code, we must expect it to return 100,000. But I ran this code in the JDK1.8 environment and verified it many times, and there were two results:

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class T02_CopyOnWriteList {
	public static void main(String[] args) {
		 List<String> lists =
				new ArrayList<>(); //这个会出并发问题!
				//new Vector();
				//new CopyOnWriteArrayList<>();
	/*	List<String> strs = new ArrayList<>();
		List<String> lists = Collections.synchronizedList(strs);*/
		Random r = new Random();
		Thread[] ths = new Thread[100];
		
		for(int i=0; i<ths.length; i++) {
			Runnable task = new Runnable() {
	
				@Override
				public void run() {
					for(int i=0; i<1000; i++) lists.add("a" + r.nextInt(10000));
				}
				
			};
			ths[i] = new Thread(task);
		}
		
		
		runAndComputeTime(ths);
		
		System.out.println(lists.size());
	}
	
	static void runAndComputeTime(Thread[] ths) {
		long s1 = System.currentTimeMillis();
		Arrays.asList(ths).forEach(t->t.start());
		Arrays.asList(ths).forEach(t->{
			try {
				t.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		});
		long s2 = System.currentTimeMillis();
		System.out.println(s2 - s1);
		
	}
}

It can be concluded from the above experiment that, except for ArrayList, other collections are thread-safe in the case of multi-threading

The first: throw an array out of bounds exception

Exception in thread "Thread-4" java.lang.ArrayIndexOutOfBoundsException: 163
    at java.util.ArrayList.add(ArrayList.java:459)
    at com.mashibing.juc.c_025.T02_CopyOnWriteList$1.run(T02_CopyOnWriteList.java:31)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 1851
    at java.util.ArrayList.add(ArrayList.java:459)
    at com.mashibing.juc.c_025.T02_CopyOnWriteList$1.run(T02_CopyOnWriteList.java:31)
    at java.lang.Thread.run(Thread.java:748)

The second type: the collection length is less than 100,000

 

Why is this? Let's take a look at part of the source code of ArrayList:

//存放list集合元素的数组,默认容量10
transient Object[] elementData; 
//list大小
private int size;

Let's take a look at the add source code again:

public boolean add(E e) {
    //确定添加元素之后,集合的大小是否足够,若不够则会进行扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //插入元素
    elementData[size++] = e;
    return true;
}

Now, suppose there are two threads inserting values ​​into the list. At this time, the size obtained by thread A is 9, and the size obtained by thread B is also 9, but thread A is time sliced ​​after the execution of ensureCapacityInternal(size + 1) When it is used up, thread B can execute. At this time, thread B finds that size+1=10, which just meets the capacity size and does not need to be expanded. At this time, thread A gets the time slice, and then it executes elementData[size++] = e, However, the size is now 10, and the array will be out of bounds when inserting. In addition, we found that the size field is not modified with volatile, size++ itself is non-atomic, and access conflicts between multiple threads. At this time, two threads may assign a value to the same location, and the result that size is smaller than the expected value may occur.

synchronizedList()&CopyOnWriteArrayList

In actual projects, List is a container we use very frequently. If in a concurrent environment, how do we get a thread-safe List container? I believe everyone knows a class like Collections. Using it we can obtain a thread-safe List container—Collections.synchronizedList(List<T> list), but whether it is reading or writing, it will be locked. When we are concurrent The level is extremely high, and threads will wait on any operation, so it is not the best choice in some scenarios. In many scenarios, our read operations may be far greater than write operations. At this time, using this method obviously does not satisfy us, so what should we do? Don't worry, the JDK has already considered it for us. In order to maximize the performance of reading, it provides the CopyOnWriteArrayList class. This class is not mutually exclusive between reading and reading and, more importantly, reading and writing are not mutually exclusive. Reprimand. Below, let's take a look at how it does it:

public boolean add(E e) {
    //获取重入锁
    final ReentrantLock lock = this.lock;
    //加锁
    lock.lock();
    try {
        //得到旧数组并获取旧数组的长度
        Object[] elements = getArray();
        int len = elements.length;
        //复制旧数组的元素到新的数组中并且大小在原基础上加1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //把值插入到新数组中
        newElements[len] = e;
        //使用新数组替换老数组
        setArray(newElements);
        return true;
    } finally {
        //释放锁
        lock.unlock();
    }
}

From the source code, we can see that the reentrant lock is used in the add operation, but this lock is only for write-write operations. Why is there no mutual exclusion between reading and writing? The key is that the operation of adding values ​​is not done directly in the original array, but using the original array to copy a new array, then insert the value into the new array, and finally use the new The array replaces the old array, and the insertion is complete. You can find that using this method, the old array is not modified during the add process, so the write operation does not affect the read operation. In addition, the array defines a private transient volatile Object[] array, which uses volatile modification to ensure that the memory is visible Therefore, the reading thread can immediately know this modification. Let's take a look at the read operation:

public E get(int index) {
    return get(getArray(), index);
}
private E get(Object[] a, int index) {
    return (E) a[index];
}
final Object[] getArray() {
    return array;
}

The read operation does not use any synchronization control or locking at all. This is because the internal structure of the array will not change, and will only be replaced by another array, so the read is thread-safe.

You can use the two methods described above to test whether the results are the same as we expected. In addition, about these two methods, you can test their performance in the case of more reading and less writing and more writing and less reading.

to sum up

In the JDK, to obtain a thread-safe List, we can use the Collections.synchronizedList (List<T> list) method or the CopyOnWriteArrayList class.

In a real environment, they can be used according to our business needs. When inserting operations far exceed reading, it is recommended to use synchronizedList. This is because CopyOnWriteArrayList will create a new array during the inserting process, so the amount of data is particularly large. In this case, the memory consumption is very large. Of course, if the read operation is much larger than the insert, the second method is definitely more dominant. After all, the read operation does not need to be locked.

In a word: read more and write less: use CopyOnWriteArrayList 

              Write more and read less: use synchronizedList

Of course, which is faster in the end is still subject to the actual pressure test results

 

Guess you like

Origin blog.csdn.net/zhaofuqiangmycomm/article/details/113102649