Diligence can make up for the lack of intelligence, but intelligence can not make up for the defect of laziness. Hello, I’m Mengyangchen! Looking forward to meeting you!

The article directories

  • An overview of the

  • 01 Java multithreading review

  • wait/sleep

  • 02. Producer and consumer review

  • 03. New version of producer and consumer writing (using Lock,Condition)

  • Advantages of the new writing method

  • Lock 04.

  • The list thread is not safe to handle

  • 06. The set thread is not handled safely

  • 07. The map thread is not processed safely

An overview of the

JUC stands for java.util.Concurrent toolkit, commonly known as Java and package issuing. This is a toolkit for working with threads, which started with JDK 1.5.

01 Java multithreading review

Interface Lock

The Lock implementation provides a wider range of locking operations than can be obtained using synchronized methods and statements. They allow for more flexible structuring, may have completely different attributes, and can support multiple related objects, conditions.

Locks are tools for controlling access to shared resources across multiple threads. Typically, locks provide exclusive access to a shared resource: only one thread can acquire the lock at a time, and all access to the shared resource requires the lock to be acquired first. However, some locks may allow concurrent access to shared resources, such as the read lock for ReadWriteLock.

Lock l = ... ; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }Copy the code

Lock interface implementation Class: Class ReentrantLock repeatable Lock

  class X {
private final ReentrantLock lock = new ReentrantLock();
// ... public void m() { 
lock.lock(); 
// block until condition holds 
try {
 // ... method body 
} finally {
    lock.unlock() 
}
Copy the code

Lock Narrows the resource scope of the Lock.

The start () thread does not start immediately, but just enters the ready state. And so on operating system scheduling.

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; Class Ticket {// Private int number = 300; private Lock lock= new ReentrantLock(); public void saleTicket(){ lock.lock(); Try {if(number>0){system.out.println (thread.currentThread ().getName())+" +"; } } finally { lock.unlock(); }}} /* With high cohesion and low coupling, Thread Operation Resource class */ Public Class SaleTicket {public static void main(String[] args) {Ticket Ticket = new Ticket(); new Thread(new Runnable (){ @Override public void run() { for(int i=1; i<=300; i++){ ticket.saleTicket(); } } }, "t1").start(); new Thread(new Runnable (){ @Override public void run() { for(int i=1; i<=300; i++){ ticket.saleTicket(); } } }, "t2").start(); new Thread(new Runnable (){ @Override public void run() { for(int i=1; i<=300; i++){ ticket.saleTicket(); } } }, "t3").start(); }}Copy the code

Thread states: NEW (created), RUNNABLE (ready to run),BLOCKED (BLOCKED),

WAITING(silly WAITING),TIMED_WAITING

TERMINATED(to be TERMINATED, meaning execution ends or the thread is dead)

wait/sleep

The current thread is suspended. What’s the difference?

Let go of the lock in my hand

I still have the lock in my hand

Lamda expression:

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; Class Ticket {// Private int number = 300; private Lock lock= new ReentrantLock(); public void saleTicket(){ lock.lock(); Try {if(number>0){system.out.println (thread.currentThread ().getName())+" +"; } } finally { lock.unlock(); }}} /* With high cohesion and low coupling, Thread Operation Resource class */ Public Class SaleTicket {public static void main(String[] args) {Ticket Ticket = new Ticket(); new Thread(()->{ for(int i=1; i<=300; i++){ ticket.saleTicket(); } }, "t1").start(); new Thread(()->{ for(int i=1; i<=300; i++){ ticket.saleTicket(); } }, "t2").start(); new Thread(()->{ for(int i=1; i<=300; i++){ ticket.saleTicket(); } }, "t3").start(); }}Copy the code

Functional interfaces, only one method, java8 after the interface can have implementation method (default).

02. Producer and consumer review

Judge/work/inform

Case production: the merchant produces a cake and the user consumes one.

class Cake{ private int number =0; Public synchronized void increment() throws InterruptedException {// Determine if(number! =0){ this.wait(); } // otherwise generate cake number++; System.out.println(Thread.currentThread().getName()+"\t"+number); // notifyAll(); // notifyAll(); } public synchronized void decrement() throws InterruptedException {// Check if(number==0){this.wait(); } // consume number--; System.out.println(Thread.currentThread().getName()+"\t"+number); // Notification, which can be prepared to consume this.notifyall (); } } public class Foods { public static void main(String[] args) { Cake cake = new Cake(); new Thread(()->{ for (int i=1; i<=10; i++){ try { cake.increment(); } catch (InterruptedException e) { e.printStackTrace(); }}}," make a cake ").start(); new Thread(()->{ for (int i=1; i<=10; i++){ try { cake.decrement(); } catch (InterruptedException e) { e.printStackTrace(); }}}," buy cake ").start(); }}Copy the code

If you have multiple chefs and multiple consumers.

In multithreaded interactions, false awakenings by multiple threads must be prevented

You must use “while” instead of “if”, otherwise you will make an error

.

class Cake{ private int number =0; Public synchronized void increment() throws InterruptedException { =0){ this.wait(); } // otherwise generate cake number++; System.out.println(Thread.currentThread().getName()+"\t"+number); // notifyAll(); // notifyAll(); } public synchronized void decrement() throws InterruptedException {// Judge while (number==0){this.wait(); } // consume number--; System.out.println(Thread.currentThread().getName()+"\t"+number); // Notification, which can be prepared to consume this.notifyall (); } } public class Foods { public static void main(String[] args) { Cake cake = new Cake(); new Thread(()->{ for (int i=1; i<=10; i++){ try { cake.increment(); } catch (InterruptedException e) { e.printStackTrace(); }}},"A1 make cake ").start(); new Thread(()->{ for (int i=1; i<=10; i++){ try { cake.decrement(); } catch (InterruptedException e) { e.printStackTrace(); }}},"B1 buy cake ").start(); new Thread(()->{ for (int i=1; i<=10; i++){ try { cake.increment(); } catch (InterruptedException e) { e.printStackTrace(); }}},"A2 make cake ").start(); new Thread(()->{ for (int i=1; i<=10; i++){ try { cake.decrement(); } catch (InterruptedException e) { e.printStackTrace(); }}},"B2 buy cake ").start(); }}Copy the code

03. New version of producer and consumer writing (using Lock,Condition)

public interface

ConditionCondition factors the Object monitor methods (wait, notify, and notifyAll) into different objects to get each Object with multiple wait sets by locking them together with any combination of effects. Lock replaces the use of synchronized methods and statements, and Condition replaces the use of object monitor methods.

The official way is very official:

class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); }}}Copy the code

After the improvement:

package Part1; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Food{ private int number = 0; private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); Private Condition condition2 = condition.newcondition (); // Consumer public void increment() throws InterruptedException {lock.lock(); try{ while (number! =0){ condition1.await(); } number++; System.out.println(thread.currentThread ().getName()+" production \t"+number); condition2.signal(); // Wake up the consumer}finally {lock.unlock(); Public void decrement() throws InterruptedException {lock.lock(); try{ while (number==0){ condition2.await(); } number--; System. The out. Println (Thread. CurrentThread (). The getName () + "\ t" + number); condition1.signal(); }finally { lock.unlock(); } } } public class Produce_customer { public static void main(String[] args) { Food food = new Food(); new Thread(()->{ for(int i=1; i<=30; i++){ try { food.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); new Thread(()->{ for(int i=1; i<=30; i++){ try { food.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"t2").start(); }}Copy the code

Advantages of the new writing method

Accurate notification, accurate wake-up.

Example: Sequential calls between multiple threads

Thread A prints 5 times, thread B prints 10 times, and thread C prints 15 times.

package Part1; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class First{ private int state = 1; Private Lock Lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); public void print(int num) { int temp = 1; if(num==5){ temp = 1; }else if(num==10){ temp = 2; }else{ temp = 3; } lock.lock(); // try {while(state! =temp) { if (num == 5) { condition1.await(); } else if (num == 10) { condition2.await(); } else { condition3.await(); For (int I =0; i<num; I++){system.out.println (thread.currentthread ().getname ()+" work!" ); } if(num==5){ state = 2; condition2.signal(); }else if(num==10){ state = 3; condition3.signal(); }else{ state = 1; condition1.signal(); } } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } public class Multithreading { public static void main(String[] args) { First first = new First(); new Thread(()->{ for(int i=0; i<5; i++){ first.print(5); } },"A").start(); new Thread(()->{ for(int i=0; i<5; i++){ first.print(10); } },"B").start(); new Thread(()->{ for(int i=0; i<5; i++){ first.print(15); } },"C").start(); }}Copy the code

Lock 04.

Synchronized locks the entire resource class, even though it is in methods.

If there are multiple synchronized methods in an object, any thread that calls one of the synchronized methods at any given time must wait. In other words, only one thread can access the synchronized methods at any given time

The lock is on the current object this, and after this is locked, no other thread can access other synchronized methods on the current object

If you add a normal method, it doesn’t have anything to do with a synchronous lock. If you change it to two objects, it doesn’t have the same lock.

All non-statically synchronized methods use the same lock — the instance object itself, the basis of synchronized synchronization: every object in Java can be used as a lock.

For normal synchronization methods, the lock is the current instance object. For statically synchronized methods, the lock is the class object of the current class. For synchronized method blocks, the lock is an object configured in Synchonized parentheses.

When a thread attempts to access a synchronized block of code, it must first acquire the lock and release it when it exits or throws an exception.

That is, if a non-statically synchronized method of an instance acquires a lock, the other non-statically synchronized methods of the instance must wait for the method that acquires the lock to release the lock. However, the non-statically synchronized methods of other instances use a different lock than the non-statically synchronized methods of the instance. So non-statically synchronized methods that must wait for the instance object to acquire the lock release the lock before they can acquire their own lock.

All statically synchronized methods use the same lock on the —- Class object itself. The two locks (this/Class) are two different objects, so there is no race condition between statically synchronized methods and non-statically synchronized methods.

But once a statically synchronized method acquires the lock, all other statically synchronized methods must wait for that method to release the lock before acquiring it, whether between statically synchronized methods of the same instance object or between statically synchronized methods of different instance objects, as long as they are of the same class!

The list thread is not safe to handle

package Part2; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class NotSafeList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for(int i=1; i<=3; I++) {new Thread (() - > {list. Add (UUID. RandomUUID (), toString (). The substring (0, 8)); System.out.println(list); },String.valueOf(i)).start(); }}}Copy the code

The results of each execution are inconsistent. There are problems.

If the number of threads increases: directly report the following exception



Concurrent modification is abnormal.

Solutions:

Method 1: Use a Vector collection, which is thread-safe with a synchronization lock. But access performance degrades.

package Part2; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.Vector; public class NotSafeList { public static void main(String[] args) { List<String> list = new Vector<>(); for(int i=1; i<=30; I++) {new Thread (() - > {list. Add (UUID. RandomUUID (), toString (). The substring (0, 8)); System.out.println(list); },String.valueOf(i)).start(); }}}Copy the code

Method 2: Use the collection utility class although it is more efficient than ventor. Data consistency decreases.

package Part2; import java.util.*; public class NotSafeList { public static void main(String[] args) { List<String> list = Collections.synchronizedList(new  ArrayList<>()); for(int i=1; i<=30; I++) {new Thread (() - > {list. Add (UUID. RandomUUID (), toString (). The substring (0, 8)); System.out.println(list); },String.valueOf(i)).start(); }}}Copy the code

Class CopyOnWriteArrayList

(key)

package Part2; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; public class NotSafeList { public static void main(String[] args) { List<String> list =new CopyOnWriteArrayList<>(); for(int i=1; i<=30; I++) {new Thread (() - > {list. Add (UUID. RandomUUID (), toString (). The substring (0, 8)); System.out.println(list); },String.valueOf(i)).start(); }}}Copy the code

Read/write separation:

public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); }}Copy the code

Make a copy for writing, and the original copy for reading. After writing, the latest version replaces the original version.

When writing copy

A Copyonwrite container is a container for copying while writing. When adding an element to a container, instead of adding object[] directly to the current container, icopy first adds mobject[] to the current container. SetArray (newELements); setArray(newELements); setArray(newELements); . The advantage of this is that concurrent reads can be made to the Copyonwrite container without locking, since no elements are being added to the current container. So the Copyonwrite container is also an idea of read-write separation, reading and writing different containers

06. The set thread is not handled safely

Solution one: Use the Collections collection class

package Part2; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.UUID; public class NotSafeSet { public static void main(String[] args) { Set<String> set = Collections.synchronizedSet(new HashSet<>()); for(int i=1; i<=30; I++) {new Thread (() - > {set. The add (UUID. RandomUUID (), toString (). The substring (0, 8)); System.out.println(set); },String.valueOf(i)).start(); }}}Copy the code

Method two: Use the JUC class java.util.concurrent

Class CopyOnWriteArraySet<E>
Copy the code

Example:

package Part2; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; public class NotSafeSet { public static void main(String[] args) { Set<String> set = new CopyOnWriteArraySet<>(new HashSet<>()); for(int i=1; i<=30; I++) {new Thread (() - > {set. The add (UUID. RandomUUID (), toString (). The substring (0, 8)); System.out.println(set); },String.valueOf(i)).start(); }}}Copy the code

Underneath a HashSet is a HashMap

But Set is a value, and HashMap is a key-value pair. The underlying HashSet calls the PUT method of the HashMap.

The value is stored on the key, which is an object PRESENT constant.

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }


private static final Object PRESENT = new Object();
Copy the code

The underlying elements of a HashMap are arrays, linked lists, and red-black trees of Nodes

HashMap() constructs an empty HashMap, Default initialCapacity (16) and default loadFactor (0.75) HashMap(int initialCapacity, float loadFactor) constructs an empty HashMap with the specified initialCapacity and loadFactor.Copy the code

Used in actual development:

HashMap(int initialCapacity, float loadFactor) constructs an empty HashMap with the specified initialCapacity and loadFactorCopy the code

07. The map thread is not processed safely

Not solve safety problems: method 1: use the HashTable method 2: use the Collections of the Collections, synchronizedMap (new HashMap < > ());

Method 3:

java.util.concurrent 
Class ConcurrentHashMap<K,V>
Copy the code

ArrrayList has been expanded by half and HashMap has been expanded by twice.

The underlying principles of HashMap

Don’t let dream just be your dream.