5. Collection thread safety
-
Thread unsafe demo for collection
- ArrayList
package com.codetip.codejuc.juc.conllections; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class ThreadArrayList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i <= 100; i++) { new Thread(() -> { // Add content to thread list.add(UUID.randomUUID().toString().substring(0, 8)); // Take the content out of it System.out.println(list); // When the collection is unsafe, a thread is still putting in the content, resulting in concurrent modification }, String.valueOf(i)).start(); } } }
The operation results are as follows:
-
Solution:
- Use vector (infrequently - inefficient)
public class ThreadArrayListOnVector { public static void main(String[] args) { List<String> list = new Vector<>(); // Using vector for (int i = 0; i <= 100; i++) { new Thread(() -> { // Add content to thread list.add(UUID.randomUUID().toString().substring(0, 8)); // Take the content out of it System.out.println(list); }, String.valueOf(i)).start(); } } }
- Using Collections
public class ThreadArrayListOnCollections { public static void main(String[] args) { // Tool classes through Collections Toolkit List<String> list = Collections.synchronizedList(new ArrayList<>()); for (int i = 0; i <= 100; i++) { new Thread(() -> { // Add content to thread list.add(UUID.randomUUID().toString().substring(0, 8)); // Take the content out of it System.out.println(list); }, String.valueOf(i)).start(); } } }
-
Use copy on write technology (CopyOnWriteArrayList in JUC)
- Overview: multiple threads read the same content when reading, copy the same content before writing, and overwrite or merge the content before writing. The code is as follows:
public static void main(String[] args) { // Using write time replication technology in Juc List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i <= 100; i++) { new Thread(() -> { // Add content to thread list.add(UUID.randomUUID().toString().substring(0, 8)); // Take the content out of it System.out.println(list); }, String.valueOf(i)).start(); } }
The source code is as follows:
// CopyOnWriteArrayList add method source code public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock();// Lock try { Object[] elements = getArray();// Gets the array of the current collection int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); // Make a new copy newElements[len] = e; // Write new content setArray(newElements); // (reassignment) content before merging return true; } finally { lock.unlock(); // Unlock } }
-
HashSet
-
Thread unsafe demo
// Demonstrate thread insecurity in HashSet public static void main(String[] args) { Set<String> list = new HashSet<>(); for (int i = 0; i <= 100; i++) { new Thread(() -> { // Add content to thread list.add(UUID.randomUUID().toString().substring(0, 8)); // Take the content out of it System.out.println(list); }, String.valueOf(i)).start(); } }
-
Solution
- Use CopyOnWriteArraySet in Juc to solve the problem
public static void main(String[] args) { Set<String> list = new CopyOnWriteArraySet<>(); for (int i = 0; i <= 100; i++) { new Thread(() -> { // Add content to thread list.add(UUID.randomUUID().toString().substring(0, 8)); // Take the content out of it System.out.println(list); }, String.valueOf(i)).start(); } }
-
The bottom layer of HashSet is actually a HashMap, and the value of HashMap is the value of HashSet. The Key of HashMap is non repeatable and unordered.
The source code is as follows:
private transient HashMap<E,Object> map; public boolean add(E e) { return map.put(e, PRESENT)==null; }
-
-
HashMap
-
Thread unsafe demo
// HashMap thread unsafe demo public static void main(String[] args) { Map<String, String> map = new HashMap<>(); for (int i = 0; i <= 100; i++) { String key = String.valueOf(i); new Thread(() -> { // Add content to thread map.put(key, UUID.randomUUID().toString().substring(0, 8)); // Take the content out of it System.out.println(map); }, String.valueOf(i)).start(); } }
-
Solution: use the ConcurrentHashMap in the Juc package
public static void main(String[] args) { Map<String, String> map = new ConcurrentHashMap<>(); // ConcurrentHashMap in JUC package for (int i = 0; i <= 100; i++) { String key = String.valueOf(i); new Thread(() -> { // Add content to thread map.put(key, UUID.randomUUID().toString().substring(0, 8)); // Take the content out of it System.out.println(map); }, String.valueOf(i)).start(); } }
-
6. Multithreaded lock
-
Demonstration of eight locks
package com.codetip.codejuc.juc; import java.util.concurrent.TimeUnit; class Phone { public synchronized void sendSms() throws InterruptedException { // 2. Stop for four seconds and print SMS or email first // Stay for four seconds TimeUnit.SECONDS.sleep(4); System.out.println("Send SMS-----send_SMS"); } public synchronized void sendEmail() { System.out.println("Send mail-----send_Email"); } public static synchronized void sendSmsS() throws InterruptedException { // 2. Stop for four seconds and print SMS or email first // Stay for four seconds TimeUnit.SECONDS.sleep(4); System.out.println("Send SMS-----send_SMS"); } public static synchronized void sendEmailS() { System.out.println("Send mail-----send_Email"); } public void getHello() { System.out.println("-------getHello"); } } /** * 8 lock * 1. A mobile phone, standard access, print SMS or email first, comment out the dwell time first, and then open it all * Send SMS ----- send_SMS * Send mail ----- send_Email * * 2.A mobile phone, stay for four seconds, print SMS or email first * Send SMS ----- send_SMS * Send mail ----- send_Email * Cause analysis: 1 and 2. Execution sequence: the synchronized method on the first sms execution method has locked the current object, and Email is waiting to obtain the lock * synchronized The lock is the current object * * 3.Add a common Hello method. Print SMS or hello first * -------getHello * Send SMS ----- send_SMS * * Reason: the common method does not lock, which has nothing to do with the lock, so it is implemented directly * * 4.Now there are two mobile phones. Print SMS or email first * Send mail ----- send_Email * Send SMS ----- send_SMS (wait four seconds) * Cause analysis: there are two objects. The synchronized lock is only the current object, and the same lock is not used, * * 5.Two static synchronization methods: 1 mobile phone, print SMS or email first * Send SMS ----- send_SMS * Send mail ----- send_Email * * 6.Two static synchronization methods, two mobile phones, print SMS or email first * Send SMS ----- send_SMS * Send mail ----- send_Email * * Cause analysis 5 and 6 static synchronized locked ranges send changes, not the current object this, but the Class object of the current Class (bytecode object of the Class) * * 7. A static synchronization method, a common method and a mobile phone. Print SMS or email first * Send mail ----- send_Email * Send SMS ----- send_SMS * * 8.A static synchronization method, a common method, 2 mobile phones, print SMS or email first * Send mail ----- send_Email * Send SMS ----- send_SMS (stay for four seconds) * * Cause analysis: 7 and 8 still have different scope of lock. static synchronized locks the Class object of the Class, and the current object this without static locking * */ public class EightLock { public static void main(String[] args) throws InterruptedException { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { try { // 1. Standard access, print SMS or email first // 2. Stop for four seconds and print SMS or email first // 3. Add a common Hello method, whether to print SMS or hello first //phone1.sendSms(); // 5. Two static synchronization methods: one mobile phone, print SMS or email first // 6. Two static synchronization methods, two mobile phones, print SMS or email first // 7. A static synchronization method, a common method and a mobile phone. Print SMS or email first // 8. One static synchronization method, one common method and two mobile phones. Print SMS or email first phone1.sendSmsS(); } catch (Exception e) { e.printStackTrace(); } }, "AA").start(); Thread.sleep(100); new Thread(() -> { try { // 1. Standard access, print SMS or email first // 2. Stop for four seconds and print SMS or email first // phone1.sendEmail(); // 3. Add a common Hello method, whether to print SMS or hello first // phone1.getHello(); // 4. Now there are two mobile phones. Print SMS or email first // phone2.sendEmail(); // 5. Two static synchronization methods: one mobile phone, print SMS or email first // phone1.sendEmailS(); // 6. Two static synchronization methods, two mobile phones, print SMS or email first // phone2.sendEmailS(); // 7. A static synchronization method, a common method and a mobile phone. Print SMS or email first // phone1.sendEmail(); // 8. One static synchronization method, one common method and two mobile phones. Print SMS or email first phone2.sendEmail(); } catch (Exception e) { e.printStackTrace(); } }, "BB").start(); } }
Summary: synchronized is the basis of synchronization: every object in Java can be used as a lock, which is shown in the following three cases
1. For the normal method: the current instance object of the lock (commonly known as This) 2. For static synchronization methods: the lock is the Class object of the current Class 3. For the synchronization method block, the locked object is the configured object in the synchronized bracket -
Fair lock and unfair lock
-
The specific codes are as follows
// The default is a non fair lock private final Lock lock = new ReentrantLock(); // The source code is as follows public ReentrantLock() { sync = new NonfairSync(); } // How to create a class using a fair lock, pass in true in the construction parameter to indicate the use of a fair lock public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } // The source code is as follows static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); // Attempt to acquire lock } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // Judge whether the lock is used if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // Someone lined up with else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
-
characteristic:
- Unfair lock features: other threads are starved to death and are highly efficient. (refer to the example of selling tickets)
- Fair lock features: each thread is likely to get more, and the efficiency is slightly lower. (refer to the example of selling tickets)
-
-
Reentrant lock (recursive lock)
- synchronized implicit reentrant lock (locking and unlocking are done automatically)
- Lock: displayed reentrant lock
Code demonstration: synchronized
// Case 1 synchronization code block Object o = new Object(); new Thread(() -> { synchronized (o) { System.out.println(Thread.currentThread().getName() + "Outer layer"); synchronized (o) { System.out.println(Thread.currentThread().getName() + "middle level"); synchronized (o) { System.out.println(Thread.currentThread().getName() + "Inner layer"); } } } }, "aa").start(); // Case 2 synchronization method public class ReSyn { public synchronized void add() { add(); } // Use the synchronized keyword to demonstrate reentrant locks public static void main(String[] args) { new ReLock().add(); } } // Case 2: memory overflow occurs during operation. Reason: synchronized is a reentrant lock. The locked add method can also be called (reentrant lock (recursive lock)) because it is the same object
Code demo: Lock
Lock lock = new ReentrantLock(); // Create thread new Thread(() -> { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "Outer layer"); try { lock.lock(); System.out.println(Thread.currentThread().getName() + "Inner layer"); } finally { lock.unlock(); } } finally { lock.unlock(); } }, "aa").start(); }
Demo lock does not close:
// Use the Lock keyword to Lock without closing public static void main(String[] args) { Lock lock = new ReentrantLock(); // Create thread new Thread(() -> { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "Outer layer"); try { lock.lock(); System.out.println(Thread.currentThread().getName() + "Inner layer"); } finally { // If the lock inside doesn't close // lock.unlock(); } } finally { lock.unlock(); } }, "AA").start(); // Create a new thread new Thread(() -> { try { lock.lock(); System.out.println(Thread.currentThread().getName() + ""); } finally { lock.unlock(); } }, "BB").start(); } }
The results are as follows: a deadlock occurs. The reason: the above thread is not released, and the following thread cannot obtain the lock.
-
Deadlock when two or more threads are executing, they are waiting for each other because they compete for resources. If there is no interference from external forces, they can no longer execute.
-
Causes of Deadlock:
- Insufficient system resources
- The thread running sequence is incorrect
- Improper allocation of resources
-
Code demonstration:
package com.codetip.codejuc.juc.deallock; import java.util.concurrent.TimeUnit; public class DealLock { static Object a = new Object(); static Object b = new Object(); public static void main(String[] args) { // Hold lock a and wait for lock b new Thread(() -> { synchronized (a) { System.out.println(Thread.currentThread().getName() + "Hold lock a,Attempt to acquire lock b"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (b) { System.out.println(Thread.currentThread().getName() + "Acquire lock b"); } } }, "aa").start(); // Hold lock b and wait for lock a new Thread(() -> { synchronized (b) { System.out.println(Thread.currentThread().getName() + "Hold lock b,Attempt to acquire lock a"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (a) { System.out.println(Thread.currentThread().getName() + "Acquire lock a"); } } }, "bb").start(); } }
The operation screenshot is as follows:
-
Verify deadlock
-
Command: use jps (similar to ps -ef in Linux)
-
Stack trace command in jstack jvm
Specific demonstration:
-
Command input: jps -l view the process ID of the class to be observed, where the ID is: 3880
-
Using jstack 3880, you can see the specific information of deadlock
-
-
-