java multi-threaded summary-comparison and introduction of synchronization container and concurrent container

Posted by phpete2 on Fri, 09 Aug 2019 04:25:06 +0200

1 Container Set Brief Introduction

There are two main container sets under the java.util package: List and Set under the Collection interface and Map.
The general structure is as follows:

  • Collection

    • List
      • LinkedList
      • ArrayList
      • Vector
        • Stack
    • Set
      • HashSet
      • TreeSet
      • LinkedSet
  • Map

    • Hashtable
    • HashMap
    • WeakHashMap

2 Synchronization container

Synchronization containers, also known as thread-safe containers, secure threads by locking thread-unsafe operations with the syncrhoized keyword
Among them, the synchronization container mainly includes:
1.Vector,Stack,HashTable
2. Synchronized collection classes provided in the Collections tool class
The Collections class is a tool class equivalent to the Arrays class's support for Array, which provides a number of ways to sort and find collections or containers.It also provides several static methods for creating synchronous container classes:

3 Concurrent container

java.util.concurrent provides a variety of thread-safe containers, most of which are thread-safe using underlying system technologies, also known as concurrent containers, similar to native.CAS is used in Java8.

4 Case Explanations

Some common synchronization and concurrency containers are mainly introduced here, which are compared by case output results.
I have roughly three types of Map/Set,List,Queue to explain, but a Map/Set, only Map, because in the design of java, Set is Map, which means only Key has no Value. Okay, let's get to the point now

4.1 Map/Set

There are three new Maps in the code, HashTable, ConcurrentHashMap, ConcurrentSkipListMap, which compare the efficiency of each map. From 100 threads, 10,000 random numbers are stored in the map. CountDownLatch is used to control the running state and output the running time.

/**
 * Concurrent Container - ConcurrentMap
 */
package com.bernardlowe.concurrent.t06;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch;

public class Test_01_ConcurrentMap {
	
	public static void main(String[] args) {
		final Map<String, String> map = new Hashtable<>();
		// final Map<String, String> map = new ConcurrentHashMap<>();
		// final Map<String, String> map = new ConcurrentSkipListMap<>();
		final Random r = new Random();
		Thread[] array = new Thread[100];
		final CountDownLatch latch = new CountDownLatch(array.length);
		
		long begin = System.currentTimeMillis();
		for(int i = 0; i < array.length; i++){
			array[i] = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = 0; j < 10000; j++){
						map.put("key"+r.nextInt(100000000), "value"+r.nextInt(100000));
					}
					latch.countDown();
				}
			});
		}
		for(Thread t : array){
			t.start();
		}
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("The execution time is: " + (end-begin) + "Millisecond!");
	}

}

Hashtable result:

ConcurrentHashMap results:

ConcurrentSkipListMap results:

At the bottom of ConcurrentHashMap is a hash-implemented synchronous Map(Set)
ConcurrentSkipListMap is a non-blocking read/write/delete Map implemented by SkipList (Jump Table) structure. Its value is stored orderly and its internal structure is composed of a vertical and horizontal chain table. In JDK1.8, ConcurrentHashMap outperforms ConcurrentSkipListMap in performance and storage space

To make the comparison of test data more intuitive, I intentionally adjust the random numbers generated here to a larger scale.It is important to note that during testing, errors may occur if machine performance is good, because System.currentTimeMillis(), which calls a native method, will depend on the implementation mechanism of the operating system to obtain the time accuracy. Why? See this article at http://blog.sina.com.cn/s/blog_6b8bd9d80101fe8t.html.But I changed System.currentTimeMillis () to System.nanoTime() as documented and found that this did not solve the problem, probably because it did not reach the nanosecond level.

4.2 List

The following code is similar to the 4.1 code and contains three new lists, ArrayList, Vector, CopyOnWriteArrayList, starting with 100 threads to store 1000 random numbers in the map, and controlling the running state by latching CountDownLatch, outputting the running time, and the length of the final list.Since ArrayList is thread insecure, try{}catch{} is required when executing with multiple threads, otherwise an error will occur because the array is out of bounds, because at the bottom of the ArrayList is a dynamically extended array of length

/**
 * Concurrent Container - CopyOnWriteList
 */
package com.bernardlowe.concurrent.t06;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;

public class Test_02_CopyOnWriteList {
	
	public static void main(String[] args) {
		 final List<String> list = new ArrayList<String>(); // Thread insecurity
//		 Final List <String> list = new Vector <> (); //Thread Security
//		Final List <String> list = new CopyOnWriteArrayList <> (); //Thread Security
		final Random r = new Random();
		Thread[] array = new Thread[100];
		final CountDownLatch latch = new CountDownLatch(array.length);
		
		long begin = System.currentTimeMillis();
		for(int i = 0; i < array.length; i++){
			array[i] = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = 0; j < 1000; j++){
						try {
							list.add("value" + r.nextInt(100000));
						} catch (Exception e) {

						}
					}
					latch.countDown();
				}
			});
		}
		for(Thread t : array){
			t.start();
		}
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("The execution time is: " + (end-begin) + "Millisecond!");
		System.out.println("List.size() : " + list.size());
	}

}

ArrayList result: Since ArrayList is thread insecure, data may be lost in a multithreaded environment

Vector results:

CopyOnWriteArrayList results:

CopyOnWriteArrayList is read-write separated, copies a new array when writing, assigns the new array to the array after insertion, modification, or removal, and reads the latest array directly when reading, so it is very inefficient when writing (although writing is slow, it deletes the array header and tail very quickly)
From the above three results, it can be seen that CopyOnWriteArrayList guarantees thread security, but its write operation is too inefficient, but it is more concurrent and better than Vector. Vector adds synchronization to the add-delete lookup methods to ensure synchronization, but every method executes with a lock.CopyOnWriteArrayList only locks on additions and deletions, but read without locks performs better than Vector in reading. CopyOnWriteArrayList supports concurrent situations with more reads and less writes, so CopyOnWriteArrayList does not have dirty reading problems.

4.3 Queue

This section focuses on some common APIs for concurrent queues

4.3.1 ConcurrentLinkedQueue

Base Chain List Synchronization Queue

Peek() ->View the first data in queue
Poll() ->Get the first data in the queue

/**
 * Concurrent Container - ConcurrentLinkedQueue
 *  Queue-list implementation. 
 */
package com.bernardlowe.concurrent.t06;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class Test_03_ConcurrentLinkedQueue {
	
	public static void main(String[] args) {
		Queue<String> queue = new ConcurrentLinkedQueue<>();

		//Add 10 more data to the queue
		for(int i = 0; i < 10; i++){
			queue.offer("value" + i);
		}
		
		System.out.println(queue);
		System.out.println(queue.size());

		// Peek() ->View the first data in the queue,
		System.out.println("First Data " + queue.peek());
		System.out.println("queue length "+ queue.size());
		System.out.println("===================");
		// Poll() ->Get the first data in the queue
		System.out.println("First Data " + queue.peek());
		System.out.println("queue length "+ queue.size());
	}

}


Result:

4.3.2 Blocking Queue LinkedBlockingQueue

Blocking queue, queue capacity is insufficient to automatically block, queue capacity is 0 to automatically block.

Put & take - automatic blocking
put auto-blocking, auto-blocking when queue capacity is full
take auto-blocking method, auto-blocking when queue capacity is 0

/**
 * Concurrent Container - LinkedBlockingQueue
 *  Block the container.
 */
package com.bernardlowe.concurrent.t06;

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class Test_04_LinkedBlockingQueue {
	
	final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
	final Random r = new Random();
	
	public static void main(String[] args) {
		final Test_04_LinkedBlockingQueue t = new Test_04_LinkedBlockingQueue();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					try {
						t.queue.put("value"+t.r.nextInt(1000));
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}, "producer").start();
		
		for(int i = 0; i < 3; i++){
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(true){
						try {
							System.out.println(Thread.currentThread().getName() + 
									" - " + t.queue.take());
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}, "consumer"+i).start();
		}
	}

}

Result:

The result is a simple producer-consumer

4.3.3 BlockingQueue

Bounded queues implemented by the underlying array, blocking when capacity is low, have different characteristics depending on the calling API (add/put/offer)
Here, three api methods, add, put, offer, are introduced.

  • The add method throws an exception when the capacity is insufficient.
  • The put method is blocked waiting when it has insufficient capacity.
  • offer method

    Single parameter offer method, not blocking.Return false when capacity is insufficient.Current new data operation aborted.
    The three-parameter offer method (offer(value,times,timeunit)), blockages the times for a long time (in time units) when the capacity is insufficient, and returns true if the capacity is idle during the blocking time.If no capacity is idle within the blocking time range, discard the new data and return false.

/**
 * Concurrent Container - ArrayBlockingQueue
 *  Bounded container.
 */
package com.bernardlowe.concurrent.t06;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class Test_05_ArrayBlockingQueue {
	
	final BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
	
	public static void main(String[] args) {
		final Test_05_ArrayBlockingQueue t = new Test_05_ArrayBlockingQueue();
		
		for(int i = 0; i < 5; i++){

			// 1.add method
			System.out.println("add method : " + t.queue.add("value"+i));

			// 2.put method
//			try {
//				t.queue.put("put"+i);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//			System.out.println("put method : " + i);

			// 3.offer method
//			System.out.println("offer method : " + t.queue.offer("value"+i));
//			try {
//				System.out.println("offer method : " +
//							t.queue.offer("value"+i, 1, TimeUnit.SECONDS));
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
		}
		
		System.out.println(t.queue);
	}

}

add method result: throw exception when capacity is insufficient

put method result: blocking wait when capacity is insufficient

Single/multiparameter offer method results:


Single parameter offer: Insufficient capacity to return results directly without blocking
Multi-parameter offer: insufficient capacity, blocking

4.3.4 Delayed Queue

Delay queue.Implement a queue with a custom processing order based on the comparison mechanism.Commonly used for timed tasks.
For example: shut down at a fixed time.
The specific sample code is as follows

/**
 * Concurrent Container - DelayQueue
 */
package com.bernardlowe.concurrent.t06;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class Test_06_DelayQueue {
	
	static BlockingQueue<MyTask_06> queue = new DelayQueue<>();
	
	public static void main(String[] args) throws InterruptedException {
		long value = System.currentTimeMillis();
		MyTask_06 task1 = new MyTask_06(value + 2000);
		MyTask_06 task2 = new MyTask_06(value + 1000);
		MyTask_06 task3 = new MyTask_06(value + 3000);
		MyTask_06 task4 = new MyTask_06(value + 2500);
		MyTask_06 task5 = new MyTask_06(value + 1500);
		
		queue.put(task1);
		queue.put(task2);
		queue.put(task3);
		queue.put(task4);
		queue.put(task5);
		
		System.out.println(queue);
		System.out.println(value);
		for(int i = 0; i < 5; i++){
			System.out.println(queue.take());
		}
	}

}

class MyTask_06 implements Delayed {
	
	private long compareValue;
	
	public MyTask_06(long compareValue){
		this.compareValue = compareValue;
	}

	/**
	 * Compare sizes.Automatically ascending
	 * Suggestions are completed in conjunction with the getDelay method.
	 * If DelayQueue is a scheduled task that needs to be completed on time, it must be completed with the getDelay method.
	 */
	@Override
	public int compareTo(Delayed o) {
		return (int)(this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
	}

	/**
	 * How to get the planned duration.
	 * The result value is returned based on the parameter TimeUnit.
	 */
	@Override
	public long getDelay(TimeUnit unit) {
		return unit.convert(compareValue - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
	}
	
	@Override
	public String toString(){
		return "Task compare value is : " + this.compareValue;
	}
	
}

Result:

4.3.5 Transfer Queue LinkedTransferQueue

Here is the main difference between the two methods, add and transfer

  • The add - queue saves data without blocking.
  • transfer - is a special method of TransferQueue.There must be a consumer (caller of take() method).
/**
 * Concurrent Container - LinkedTransferQueue
 *  Transfer Queue
 */
package com.bernardlowe.concurrent.t06;

import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue;

public class Test_07_TransferQueue {
	
	TransferQueue<String> queue = new LinkedTransferQueue<>();
	
	public static void main(String[] args) {
		final Test_07_TransferQueue t = new Test_07_TransferQueue();
		
		/*new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println(Thread.currentThread().getName() + " thread begin " );
					System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "output thread").start();

		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		try {
			t.queue.transfer("test string");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}*/
		
		new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					t.queue.transfer("test string");
					// t.queue.add("test string");
					System.out.println("add ok");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println(Thread.currentThread().getName() + " thread begin " );
					System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "output thread").start();
		
	}

}

transfer() and take() are blocking methods here, and take requests to receive data first or transfer sends data first, which will block and wait.
For example, transfer() is equivalent to calling a mobile phone. When A calls B, B must receive a phone signal to make a call, otherwise A will wait all the time
add() is equivalent to A sending a text message to B. The text message has been saved to the operator and waiting for B to receive it, whether B is online or not when sending a text message

4.3.6 SynchronousQueue

This queue has a capacity of 0 and is a special TransferQueue, which is similar to TransferQueue, but this queue must have a consumer thread.
Two more methods add,put
add method, no blocking.An exception is thrown if no consumer thread is blocking waiting for data.
put method, blocked.Block if no consumer thread is blocking waiting data.

/**
 * Concurrent Container - SynchronousQueue
 */
package com.bernardlowe.concurrent.t06;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class Test_08_SynchronusQueue {
	
	BlockingQueue<String> queue = new SynchronousQueue<>();
	
	public static void main(String[] args) {
		final Test_08_SynchronusQueue t = new Test_08_SynchronusQueue();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println(Thread.currentThread().getName() + " thread begin " );
					try {
						TimeUnit.SECONDS.sleep(2);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "output thread").start();
		
		/*try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}*/
		// t.queue.add("test add");
		try {
			t.queue.put("test put");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(Thread.currentThread().getName() + " queue size : " + t.queue.size());
	}

}

Open the comment for t.queue.add("test add"); t.queue.put("test put"); add a comment
add method exception result: because it is a zero-capacity queue

Topics: Java less Mobile