A brief talk about Juc concurrent programming — up
preface
This course will learn concurrent programming with JUC in Java with B station crazy God
The code for this course is on my personal Gitee repository
What is JUC?
- java.util.concurrent juc
- Java. Util. Concurrent. Atomic atomicity
- Java. Util. Concurrent. The locks lock
Threads may be used in normal services
Or like the Runnable interface implementation, has no return value, and is less efficient than callable
java.util.concurrent Interface Callable
Processes and threads
We all know that the core of the computer is CPU, which undertakes all the computing tasks, while the operating system is the manager of the computer, which is responsible for task scheduling, resource allocation and management, and leads the entire computer hardware
- Process is a dynamic execution process of a program with certain independent functions in a data set. It is an independent unit of the operating system for resource allocation and scheduling. It is the carrier of application program operation
- Thread is a single sequence control flow in program execution. It is the smallest unit of program execution flow and the basic unit of processor scheduling and dispatching. A process can have one or more threads that share the memory space of the program (that is, the memory space of the process in which it is running)
Process: a program, qi.exe music.exe program collection; A process can often contain multiple threads, at least one!
How many threads does Java have by default? 2 main, GC threads: open a process Typora, write, autosave (thread responsible)
For Java: Thread, Runnable, Callable Can Java really start threads? Can’t open
// the local method calls the underlying C++, because Java runs on the JVM and cannot manipulate the hardware directly
private native void start0(a);
Copy the code
How many states does a thread have?
Thread.State, as you can see, is an enumeration
public enum State {
// The thread state of the threads that have not yet been started
/ / new
NEW,
/ / run
RUNNABLE,
/ / blocking
BLOCKED,
// Wait and die
WAITING,
// Wait for timeout
TIMED_WAITING,
/ / termination
TERMINATED;
}
Copy the code
Concurrency and parallelism
- Concurrency is when two or more events occur at the same time interval -> alternately
- A core CPU, simulated out of multiple threads, fast alternate running
- Parallelism is when two or more events occur at the same time -> simultaneously
- Multi-core CPU, multiple threads executing simultaneously, thread pool
The goal of concurrent programming is to fully utilize every core of the CPU to achieve maximum processing performance
// Get the number of CPU cores
System.out.println(Runtime.getRuntime().availableProcessors());
Copy the code
Wait/sleep
1. From different classes
wait => Object
sleep => Thread
2. Release of locks
Wait releases the lock
Hold the lock to sleep, will not release!
3. The scope of use is different
Wait must be used in synchronized blocks of code
Sleep can be anywhere
4. Whether exceptions need to be caught (doubtful)
throws InterruptedException
Wait also needs to catch exceptions.
Sleep must catch exceptions
The Lock Lock
As long as it’s concurrent programming, you need locks!
Traditional synchronized locks
I’m not talking about thread pools here, but the general approach
Decouple the thread classes, eliminating the need to write a separate thread class that inherits the Runnable interface
Instead, use lambda expressions ()->{} to implement the Runnable interface to create threads
new Thread(()->{
//do something
},"Name").start();
Copy the code
Then the synchronized lock method locks the object
public synchronized void sale(a){
if(num<=0)return;
System.out.println(Thread.currentThread().getName()+"Buy the number"+(num--)+"One ticket, the rest."+num+"Ticket");
}
Copy the code
It’s the same old story about selling tickets
public static void main(String[] args) throws InterruptedException {
Ticket ticket = new Ticket();
new Thread(()->{
for(int i=0; i<100; i++){ ticket.sale();try {
Thread.sleep(10);
} catch(InterruptedException e) { e.printStackTrace(); }}},"A").start();
new Thread(()->{
for(int i=0; i<100; i++){ ticket.sale();try {
Thread.sleep(10);
} catch(InterruptedException e) { e.printStackTrace(); }}},"B").start();
new Thread(()->{
for(int i=0; i<100; i++){ ticket.sale();try {
Thread.sleep(10);
} catch(InterruptedException e) { e.printStackTrace(); }}},"C").start();
}
static class Ticket{
private int num=100;
public synchronized void sale(a){
if(num<=0)return;
System.out.println(Thread.currentThread().getName()+"Buy the number"+(num--)+"One ticket, the rest."+num+"Ticket"); }}Copy the code
The Lock Lock
Java. Util. Concurrent. The locks. The Lock is a Lock of the interface
The recommended practice is to always immediately follow the call between the lock and the try block
The most common is locking before/after lock.lock(); And unlock the lock, unlock ()
It has several implementation classes:
- ReentrantLock ReentrantLock
- ReentrantReadWriteLock ReadLock read lock
- ReentrantReadWriteLock. WriteLock write locks
Let’s take a look at the constructor for ReentrantLock
// Create an unfair lock Nonfair by default
public ReentrantLock(a) {
sync = new NonfairSync();
}
// Boolean creates a Fair lock if the value is true, or Nonfair if the value is not true
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code
Fair Lock: Fair: First come first served Non-fair Lock: Unfair: Can cut in line (default)
Use trilogy
Lock lock=new ReentrantLock();
Instantiate the lock objectlock.lock();
locked- inIn the finally
lock.unlock();
unlock
static class Ticket{
private int num=100;
// Reentrant lock
Lock lock=new ReentrantLock();
public void sale(a){
/ / lock
lock.lock();
try{
//do something business code
if(num<=0)return;
System.out.println(Thread.currentThread().getName()+"Buy the number"+(num--)+"One ticket, the rest."+num+"Ticket");
}catch(Exception e){
e.printStackTrace();
}finally{ lock.unlock(); }}}Copy the code
The difference between synchronized and lock
- Synchronized is a built-in Java keyword, and Lock is a Java class
- Synchronized cannot determine the status of obtaining a Lock. Lock can determine whether a Lock is obtained
- Synchronized willAutomatic releaseLock, lock must beHand releaseThe lock! If the lock is not released,A deadlock
- If an exception occurs in Synchronized, the lock is automatically released
- Synchronized thread 1 (acquire lock, block), thread 2 (wait, fool, etc.); Lock will not necessarily wait;
lock.tryLock()
Try to get the lock. If you can’t get it, turn around and leave
- Synchronized can reentrant, can not interrupt, unfair; Lock, reentrant Lock, judge Lock, not fair (can set yourself)
- Synchronized is good for locking a small number of code synchronization problems, Lock is good for locking a large number of synchronization code!
Producer and consumer issues
Interviews: singleton, sorting algorithm, production consumer, deadlock
Older synchronized implementation
When num is 0, the consumer waits and the producer generates the message
When num>=0, producers wait and consumers consume
Let’s take a look at the problem code
public static void main(String[] args) throws InterruptedException {
Data data = new Data();
new Thread(()->{
for(int i=0; i<100; i++){try {
data.pro();
} catch(InterruptedException e) { e.printStackTrace(); }}},"ProducerA").start();
new Thread(()->{
for(int i=0; i<100; i++){try {
data.con();
} catch(InterruptedException e) { e.printStackTrace(); }}},"ConsumerA").start();
}
static class Data{
private int num=0;
public synchronized void pro(a) throws InterruptedException {
if(num! =0){
System.out.println(Thread.currentThread().getName()+"Waiting");
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"The producer produced a message, and num="+num);
this.notifyAll();
}
public synchronized void con(a) throws InterruptedException {
if(num==0){
System.out.println(Thread.currentThread().getName()+"Waiting");
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"Consumer consumes a message, num="+num);
this.notifyAll(); }}Copy the code
Does the code work correctly at this point? It does
What if we put multiple producers and consumers?
new Thread(()->{
for(int i=0; i<100; i++){try {
data.pro();
} catch(InterruptedException e) { e.printStackTrace(); }}},"ProducerA").start();
new Thread(()->{
for(int i=0; i<100; i++){try {
data.pro();
} catch(InterruptedException e) { e.printStackTrace(); }}},"ProducerB").start();
new Thread(()->{
for(int i=0; i<100; i++){try {
data.con();
} catch(InterruptedException e) { e.printStackTrace(); }}},"ConsumerA").start();
new Thread(()->{
for(int i=0; i<100; i++){try {
data.con();
} catch(InterruptedException e) { e.printStackTrace(); }}},"ConsumerB").start();
Copy the code
And you can see that there’s a very high probability of something going wrong
The ConsumerA consumer consumes a message, num=-92, ConsumerA consumer consumes a message, num=-93, and num=-94, Num =-95 ConsumerA The consumer consumes a message, num=-96 The consumer consumes a message, num=-97 the ProducerB producer produces a message, num=-96
Is waiting for
This is a false wake up
See the API documentation for Object’s wait method
Threads can also wake up without being notified, interrupted or timed out, a so-called false wake up
For example, if an item has no goods and suddenly enters an item, all threads are awakened, but only one person can buy it, so everyone else is falsely awakened and cannot acquire the lock of the object
Although this rarely happens in practice, the application must guard against it by testing the condition that should cause the thread to wake up, and continue to wait if the condition is not met.
In other words, waiting should always be in the loop
Why is there a false wake up in the if block?
Using wait in an if block is dangerous because once the thread is woken up and the lock is acquired, it no longer evaluates the if condition and executes code outside the if block
Therefore, it is advisable to use the while loop whenever you need to make a conditional judgment and then wait
synchronized (obj) {
while(<condition does not hold>) obj.wait(timeout); .// Perform action appropriate to condition
}
Copy the code
So we changed the original code to if and changed it to while
public synchronized void pro(a) throws InterruptedException {
while(num! =0){
System.out.println(Thread.currentThread().getName()+"Waiting");
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"The producer produced a message, and num="+num);
this.notifyAll();
}
public synchronized void con(a) throws InterruptedException {
while (num==0){
System.out.println(Thread.currentThread().getName()+"Waiting");
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"Consumer consumes a message, num="+num);
this.notifyAll();
}
Copy the code
Producer and consumer implementations of the JUC version
Use the Lock and Condition interfaces
The Lock object is instantiated using ReentrantLock
The condition object is obtained using lock.newcondition ()
The Condition implementation can provide the behavior and semantics of Object monitor methods, such as guaranteed order of notifications, or not required when they are executed
lock
A Condition instance is essentially bound to a lock. To get a Condition instance of a particular Condition instance, use it
NewCondition () method
Condition 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.
static class Data{
private int num=0;
// Get locks instead of wait and notify
Lock lock=new ReentrantLock();
// Obtain condition instead of synchronized
Condition condition = lock.newCondition();
public void pro(a){
lock.lock();
try{
while(num! =0){
System.out.println(Thread.currentThread().getName()+"Waiting");
/ / wait for
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"The producer produced a message, and num="+num);
/ / wake
condition.signalAll();
}catch(Exception e){
e.printStackTrace();
}finally{ lock.unlock(); }}public void con(a){
lock.lock();
try{
while (num==0){
System.out.println(Thread.currentThread().getName()+"Waiting");
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"Consumer consumes a message, num="+num);
condition.signalAll();
}catch(Exception e){
e.printStackTrace();
}finally{ lock.unlock(); }}}Copy the code
One might ask: Synchronized is more concise, but there is also a condition, which is more troublesome.
Of course not
Difference between Lock+Condition and synchronized
Setting up multiple Condition monitors enables precise notification and wake up of threads
Personal understanding: wait when you don’t need, wake up when you need
Accurate awakening of Condition monitor
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/ * * *@Author if
* @Description: Accurate awakening using Condition *@Date 2021-11-05 上午 12:04
*/
public class Test03 {
public static void main(String[] args) {
Data data=new Data();
new Thread(()->{
for(int i=0; i<10;i++){
data.soutA();
}
},"A").start();
new Thread(()->{
for(int i=0; i<10;i++){
data.soutB();
}
},"B").start();
new Thread(()->{
for(int i=0; i<10;i++){
data.soutC();
}
},"C").start();
}
static class Data{
private Lock lock=new ReentrantLock();
private Condition condition1=lock.newCondition();
private Condition condition2=lock.newCondition();
private Condition condition3=lock.newCondition();
private int num=1;
public void soutA(a){
lock.lock();
try{
// condition1 waits when num is not 1
while(num! =1){
condition1.await();
}
System.out.println("AAAAAA");
num=2;
// Call condition2 to export B
condition2.signal();
}catch(Exception e){
e.printStackTrace();
}finally{ lock.unlock(); }}public void soutB(a){
lock.lock();
try{
// condition2 waits when num is not 2
while(num! =2){
condition2.await();
}
System.out.println("BBBBBB");
num=3;
// Call condition3 to condition3
condition3.signal();
}catch(Exception e){
e.printStackTrace();
}finally{ lock.unlock(); }}public void soutC(a){
lock.lock();
try{
Condition3 = condition3; // condition3 = condition3
while(num! =3){
condition3.await();
}
System.out.println("CCCCCC");
num=1;
// Call condition1 to output A
condition1.signal();
}catch(Exception e){
e.printStackTrace();
}finally{ lock.unlock(); }}}}Copy the code
The results of
AAAAAA BBBBBB CCCCCC AAAAAA BBBBBB CCCCCC
The condition also has awaitNanos timeout wait and awaitUntil timeout wait, as described below in ArrayBlockingQueue
Eight lock problem
1. Synchronized is the lock object synchronization
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @DescriptionTimeunit.seconds.sleep (1) was not added to the main thread; Before, the output order? Timeunit.seconds.sleep (4) was not added to the mail method; Before, the output order? Timeunit.seconds.sleep (4); timeunit.seconds. After the output order? Synchronized does not release a lock until the code is finished@Date 2021-11-05 上午 12:20
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.mail();
},"A").start();
try{
// The main thread sleeps
TimeUnit.SECONDS.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
static class Phone{
Synchronized locks the caller (instantiated object)
public synchronized void mail(a){
try{
// The lock is not released when it sleeps
TimeUnit.SECONDS.sleep(4);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("Send an email");
}
public synchronized void call(a){
System.out.println("Make a call"); }}}Copy the code
2. Synchronized does not synchronize with common methods
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @DescriptionSynchronized (mail); synchronized (call); synchronized (mail); synchronized (call); synchronized (mail)@Date 2021-11-05 上午 12:29
*/
public class Test2 {
public static void main(String[] args) {
Phone phone = new Phone();
/** * question: If there are two phone methods, mail and call methods are synchronized. In the normal order, of course, because the callers of synchronized locks, that is, instantiate objects, and there are two distinct objects */
// Phone phone1 = new Phone();
// Phone phone2 = new Phone();
// new Thread(()->{
// phone1.mail();
// },"A").start();
// try{
// //main The thread is hibernated
// TimeUnit.SECONDS.sleep(1);
// }catch(Exception e){
// e.printStackTrace();
/ /}
// new Thread(()->{
// phone2.call();
// },"B").start();
new Thread(()->{
phone.mail();
},"A").start();
try{
// The main thread sleeps
TimeUnit.SECONDS.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
static class Phone{
Synchronized locks the caller (instantiated object)
public synchronized void mail(a){
try{
// The lock is not released when it sleeps
TimeUnit.SECONDS.sleep(4);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("Send an email");
}
// There is no synchronization lock
public void call(a){
System.out.println("Make a call"); }}}Copy the code
3. Lock Class synchronized
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @Description: * * problem: mail and call method to static static method, call order * the answer: the text first, then the Phone, because at this time to lock the Phone. The class, rather than instantiation object * * problem: instantiate two objects, phone1 and phone2, call order * the answer: Again, SMS first and then call, locking Phone. Class instead of instantiating object * * *@Date 2021-11-05 上午 12:37
*/
public class Test3 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.mail();
},"A").start();
try{
// The main thread sleeps
TimeUnit.SECONDS.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.mail();
},"A").start();
try{
// The main thread sleeps
TimeUnit.SECONDS.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
static class Phone{
//synchronized locks the class, and it doesn't matter if the object is different, because multiple objects have the same class
public static synchronized void mail(a){
try{
TimeUnit.SECONDS.sleep(4);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("Send an email");
}
public static synchronized void call(a){
System.out.println("Make a call"); }}}Copy the code
4. Static synchronized and common synchronized locks
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @DescriptionMail is a static synchronized method, and call is a common synchronized method. * the answer: * The call lock is different from the mail's class lock, so it does not need to synchronize. * The call lock is different from the mail's class lock. Call * * is executed@Date 2021-11-05 上午 12:46
*/
public class Test4 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.mail();
},"A").start();
try{
// The main thread sleeps
TimeUnit.SECONDS.sleep(1);
}catch(Exception e){
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
static class Phone{
public static synchronized void mail(a){
try{
TimeUnit.SECONDS.sleep(4);
}catch(Exception e){
e.printStackTrace();
}
System.out.println("Send an email");
}
public synchronized void call(a){
System.out.println("Make a call"); }}}Copy the code
conclusion
Synchronized in a block of code or a normal method, locks the caller of the method (instantiated object)
In static methods, the class of the locked class
Object locks are different from class locks, so no synchronization is required
Unsafe List class
The collections we used were all single-threaded, so there was no problem, but many of them were unsafe
For example, we use ArrayList a lot
// Multithreaded ArrayList insertion error
/ / concurrent modification anomalies: Java. Util. ConcurrentModificationException
List<String> stringList=new ArrayList<>();
for(int i=1; i<=100; i++){new Thread(()->{
stringList.add(UUID.randomUUID().toString().substring(0.5));
System.out.println(stringList);
},i+"").start();
}
Copy the code
Single thread play more, at first glance no problem, but this is in the case of multithreading, there will be concurrent modification exception
How can you optimize it to be thread-safe?
1. Use Vector instead
Vector add, delete, modify, and check are synchronized to make the thread safe
But what about efficiency? We’ll talk about that later
// Multithreaded Vector does not report errors because synchronized is added to the underlying Vector
List<String> stringVector=new Vector<>();
for(int i=1; i<=100; i++){new Thread(()->{
stringVector.add(UUID.randomUUID().toString().substring(0.5));
System.out.println(stringVector);
},i+"").start();
}
Copy the code
2. Use Collections to synchronizedList
Use the Collections. SynchronizedList () method to convert ordinary list thread safe list
// What if I want to use a safe list
/ / use the Collections. SynchronizedList ordinary list into thread safe list
List<String> stringList=new ArrayList<>();
List<String> synchronizedList = Collections.synchronizedList(stringList);
for(int i=1; i<=100; i++){new Thread(()->{
synchronizedList.add(UUID.randomUUID().toString().substring(0.5));
System.out.println(synchronizedList);
},i+"").start();
}
Copy the code
How do you keep thread-safe and efficient
Use JUC’s CopyOnWriteArrayList
JUC: Use CopyOnWriteArrayList to solve concurrency
COW: Copy-on-write, an optimization strategy
The list is uniquely fixed; it is fixed when read by multiple threads, but may be overwritten when written
COW avoids overwriting and prevents data problems
/** * JUC: Use CopyOnWriteArrayList to handle concurrency */
List<String> list = new CopyOnWriteArrayList<>();
for(int i=1; i<=100; i++){new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0.5));
System.out.println(list);
},i+"").start();
}
Copy the code
How did it work out?
Write to copy an array of length +1, insert data at the end, and assign the array to the original array for insertion
For example, insert source code
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
How is CopyOnWriteArrayList better than vector?
Vector’s add, delete, change, and query methods all have synchronized locks, which ensure that each method has to acquire the lock in the case of synchronization, thus reducing performance
The CopyOnWriteArrayList method simply adds a ReentrantLock lock to the add, delete, and modify methods
However, its read method is unlocked, == reads and writes separate ==, so reads are better than vectors
CopyOnWriteArrayList is suitable for concurrency with too many reads and too few writes
Unsafe Set class
It’s pretty much the same as the list above, so I’m going to skip the explanation and post the code
Multithreading error
/ / concurrent modification anomalies: Java. Util. ConcurrentModificationException
HashSet<String> set = new HashSet<>();
for(int i=1; i<=100; i++){new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0.5));
System.out.println(set);
},i+"").start();
}
Copy the code
The solution
1. Turn synchronizedSet
HashSet<String> set = new HashSet<>();
Set<String> synchronizedSet = Collections.synchronizedSet(set);
for(int i=1; i<=100; i++){new Thread(()->{
synchronizedSet.add(UUID.randomUUID().toString().substring(0.5));
System.out.println(synchronizedSet);
},i+"").start();
}
Copy the code
2. Use CopyOnWriteArraySet
Set<String> set = new CopyOnWriteArraySet<>();
for(int i=1; i<=100; i++){new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0.5));
System.out.println(set);
},i+"").start();
}
Copy the code
A quick explanation of the implementation of HashSet
The implementation of HashSet is not necessarily a bonus for saying it, but a penalty for not saying it
It’s essentially a HashMap of new, and then the Object of new is the value of the HashMap, and the parameter of add is the key
Because it’s a hash algorithm, the HashSet is unordered
Since the key cannot be repeated, the elements of a HashSet cannot be repeated
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet(a) {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Copy the code
Unsafe Map class
The HashMap we often use in a single thread is also unsafe in multiple threads
/ / concurrent modification anomalies: ConcurrentModificationException
HashMap<String, String> map = new HashMap<>();
for(int i=1; i<=100; i++){new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0.5));
System.out.println(map);
}).start();
}
Copy the code
1. Use Hashtable instead
Map<String, String> hashtable = new Hashtable<>();
for (int i = 1; i <= 100; i++) {
new Thread(() -> {
hashtable.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0.5));
System.out.println(hashtable);
}).start();
}
Copy the code
As with Vector instead of ArrayList, synchronized is a simple and crude way to ensure thread-safety, though it may be less efficient
2. Turn synchronizedMap
HashMap<String, String> map = new HashMap<>();
Map<String, String> synchronizedMap = Collections.synchronizedMap(map);
for (int i = 1; i <= 100; i++) {
new Thread(() -> {
synchronizedMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0.5));
System.out.println(synchronizedMap);
}).start();
}
Copy the code
3. Use ConcurrentHashMap
Using Java. Util. Concurrent. ConcurrentHashMap
Concurrent Hashmaps, which ensure thread-safety but also efficiency, are recommended
If you’re not familiar with ConcurrentHashMap, check out my ConcurrentHashMap.
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
for (int i = 1; i <= 100; i++) {
new Thread(() -> {
concurrentHashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0.5));
System.out.println(concurrentHashMap);
}).start();
}
Copy the code
Into the Callable
A task that returns results and may throw an exception. The implementer defines a single method with no arguments, called call
Callable interfaces are similar to Runnable in that they are designed for classes whose instances might be executed by another thread
However, A Runnable does not return results and cannot throw checked exceptions
- You can have a return value
- You can throw an exception
- Methods different
- A Runnable is run ()
- Callable is call ()
The old version created threads in two ways
1.extends Thread
public static void main(String[] args) {
// The Thread class inherits Thread from the run method and starts it
new MyThread().start();
}
static class MyThread extends Thread{
@Override
public void run(a) {
System.out.println("class MyThread extends Thread"); }}Copy the code
2. Implement the Runnable interface
public static void main(String[] args) {
// The Thread class implements Runnable run, which starts the Thread by putting the instantiated object into the Thread argument
new Thread(new MyRun()).start();
}
static class MyRun implements Runnable{
@Override
public void run(a) {
System.out.println("class MyRun implements Runnable"); }}Copy the code
Create a thread using Callable
We need an adaptation class here, FutureTask
This class implements RunnableFuture, FutureTask
implements RunnableFuture
The RunnableFuture class extends Runnable, Future
So a Thread(Runnable target) can pass it in
Note: FutureTask.get () can get returned results, but may throw exceptions that need to be caught or thrown
Because it waits for execution to complete before returning, it can block and is best handled last or asynchronously
public static void main(String[] args) {
/** * Use the Callable thread class to implement Callable to implement the Call () method, which can have a return value, which is defined by the generic type * create FutureTask, Thread(Runnable target) * FutureTask
implements RunnableFuture
* RunnableFuture
extends Runnable, Future
*/
MyCall callable = new MyCall();
/ / class
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// Get return result, exception may be thrown
// It may block because it is waiting for execution to complete, so it is best to leave it at the end, or asynchronous communication to handle it
try {
Integer integer = futureTask.get();
System.out.println(integer);
} catch(InterruptedException | ExecutionException e) { e.printStackTrace(); }}static class MyCall implements Callable<Integer>{
@Override
public Integer call(a) throws Exception {
System.out.println("class MyCall implements Callable<Integer>");
return 1024; }}Copy the code
The state of the FutureTask
If we pass in two threads with the same FutureTask at this point, will the output be twice?
MyCall callable = new MyCall();
/ / class
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// What happens if you start two threads with the same futureTask object?
// The answer is: only one thread will run, and only one output
// Because the state of FutureTask has changed from initialization NEW to completion state
// If the run method is not NEW, it returns no execution
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();
Copy the code
The answer is: no, only once!
Why is that?
Let’s look at the source code
You can see that FutureTask has a state for state variables and a number of constants of type int for specific states
Here we focus only on NEW and COMPLETING
public class FutureTask<V> implements RunnableFuture<V> {
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
Copy the code
In the constructor, state is given NEW by default
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
Copy the code
The first thread comes in
In the run method, ran is set to trueIf (RAN) set(result) after the callable call method is executed.
In set method UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)
Change the NEW state into becoming well
That is to say, the FutureTask has completed its duty COMPLETING those years well
The next thread comes in and determines state! = NEW, return directly
So after executing once, no other thread can continue to execute run, the call method of the Callable
So you can conclude that normally a FutureTask can only perform one call
public void run(a) {
if(state ! = NEW || ! UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if(c ! =null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if(ran) set(result); }}finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if(s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); }}Copy the code
Commonly used helper classes
CountDownLatch subtraction counter
java.util.concurrent.CountDownLatch
Subtractive counter
A synchronization helper class that allows one or more threads to wait until a set of operations performed in another thread is complete
Can be used to enforce certain threads
CountDownLatch is initialized with the given count. The await method blocks until the current count reaches zero due to a call to the countDown() method, after which all waiting threads are released and any subsequent await calls return immediately. This is a one-time phenomenon – the count cannot be reset. If you need to reset the versions of the count, consider using CyclicBarrier.
A CountDownLatch is A universal synchronization tool that can be used for A variety of purposes. A CountDownLatch for a CountDownLatch acts as a simple open/close latch, or door: all threads call await and wait at the door until the thread called countDown() opens. A CountDownLatch initialization N can be used to do a thread wait until N threads have completed an operation or some action has been completed N times.
One useful property of CountDownLatch is that it does not require the call to the countDown thread to wait until the count reaches zero to continue, it simply blocks any thread from going through the await until all threads can go through
import java.util.concurrent.CountDownLatch;
/ * * *@Author if
* @Description: countDown lock (subtractive counter) * every time the countDown method is called -1 * blocks until the total is not zero * can be used for thread coercion (because not executing would block) *@Date 2021-11-06 上午 12:00
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//count Indicates the total number of entries
// With the await method, no more code is executed down until the countdown is over
CountDownLatch countDownLatch = new CountDownLatch(12);
for(int i=1; i<=6; i++){try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("The first"+i+"CountDown for second execution");
countDownLatch.countDown();
// new Thread(()->{
// system.out.println (thread.currentThread ().getName()+" Thread is gone ");
// // Quantity -1
// countDownLatch.countDown();
// },i+"").start();
}
// Wait for the counter to zero, and then proceed down
countDownLatch.await();
System.out.println("Closed"); }}Copy the code
CyclicBarrier addition counter
java.util.concurrent.CyclicBarrier
Addition counter
A synchronization helper class that allows a group of threads to all wait for each other to reach a common barrier point
Can be used to force waits on certain threads
Loop blocking is useful in programs involving fixed size threads that must occasionally wait for each other. A barrier is called a loop because it can be reused after the waiting thread is released.
A CyclicBarrier supports an optional Runnable command that runs once for each barrier point, after the last thread in the party arrives, but before any thread is released. This barrier operation is useful for updating the shared state before either party proceeds.
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/ * * *@Author if
* @Description: loop barrier (addition counter) * blocks the current thread with an await method * blocks until the number of times the await method is executed fails to reach the argument parties passed by the constructor *@Date 2021-11-06 上午 12:08
*/
public class CyclicBarrierDemo {
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
// Collect 7 dragon balls to summon the divine dragon
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("Collect 7 dragon balls and summon the Dragon successfully.");
});
for(int i=1; i<=7; i++){ Thread.sleep(500);
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Collect the first"+finalI+"Dragon Ball");
try {
// Wait until you have waited 7 times
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
},i+"").start(); }}}Copy the code
Semaphore Semaphore
A counting semaphore, conceptually, maintains a set of licenses.
If necessary, each acquire() blocks until a license is available before it can be used. Each release() adds a license, potentially freeing blocking receivers
However, no actual license object is used; Semaphore only keeps counts of available quantities and executes them accordingly
Semaphores are usually used to limit the number of threads, not to access some (physical or logical) resource
We assume that there are three parking Spaces and six cars that need to park, so only three cars can get in and the rest have to wait until the space is empty
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/ * * *@Author if
* @DescriptionPermits: Semaphore * parameter permits, which can be used to limit the number of threads *@Date 2021-11-06 上午 12:17
*/
public class SemaphoreDemo {
public static void main(String[] args) {
/ / permits licenses
// We assume that there are 3 parking Spaces and 6 cars to park
Semaphore semaphore = new Semaphore(3);
for(int i=1; i<=6; i++){new Thread(()->{
try{
// Acquire a license
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"Thread grabs parking space.");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"Thread leaving parking space");
}catch(Exception e){
e.printStackTrace();
}finally{
//release release license
semaphore.release();
}
},i+"").start(); }}}Copy the code