[Java|Multithreading and High Concurrency] Classes and interfaces commonly used in JUC


insert image description here

1. What is JUC?

JUC is an important module in Java concurrent programming. Its full name is Java Util Concurrent(Java Concurrency Toolkit). It provides a set of tool classes and frameworks for multi-threaded programming to help developers write thread-safe concurrent code more conveniently.

This article mainly introduces Java Util Concurrentsome commonly used interfaces and classes

2. Callable interface

The Callable interface is similar to Runnable. One difference is that the task described by Runable has no return value, while the Callable interface has a return value

Example:

Callable<返回值类型> callable = new Callable<Integer>() {
    
    
    @Override
    public 返回值类型 call() throws Exception {
    
    
       // 执行的任务      
    }
};

CallableThe interface defines a call()method, so this method must be implemented when creating an instance. This method returns a result after the task execution is completed, and can throw an exception.

Unlike Runnable, the tasks described by Callable cannot be directly passed to threads for execution. Therefore, FutureTask<T>this class is needed

FutureTask<返回值类型> futureTask = new FutureTask<>(callable);

To obtain the return value of the above task, you can use FuturTaskthe provided get method.

Example:

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        Callable<Integer> callable = new Callable<Integer>() {
    
    
            @Override
            public Integer call() throws Exception {
    
    
                int ret = 0;
                for (int i = 1; i <= 10; i++) {
    
    
                    ret += i;
                }
                return ret;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t1 = new Thread(futureTask);
        t1.start();
        System.out.println(futureTask.get());
    }

operation result:
insert image description here

3. ReentrantLock

ReentrantLock: ReentrantLock is an implementation class of the Lock interface, which implements all methods of the Lock interface. ReentrantLock supports reentrancy, which means that the same thread can acquire the same lock multiple times without deadlock. This feature allows ReentrantLock to be used in more complex thread synchronization scenarios.

In ReentrantLock, there are three very important methods:

  1. lock(): lock
  2. unlock(): unlock.
  3. tryLock(): Used to try to acquire a lock. If the lock is available, it will be acquired immediately and return true. If the lock is not available, it will immediately return false without blocking the current thread. It is also possible to specify a maximum wait time for acquiring a lock.

Unlike synchronized, its locking and unlocking operations are separate and need to be added by yourself.

This may also cause that if the code is abnormal after locking, the unlock method may not be executed. This is also ReentranLocka small disadvantage of . But we can try finallyavoid it by using it.

There are two versions of the tryLock method:
insert image description here

  • The tryLock() method without parameters is used to try to acquire the lock. If the lock is available, it will be acquired immediately and return true. If the lock is not available, it will immediately return false without blocking the current thread.

  • And another version of the tryLock() method, you can specify a timeout to try to acquire the lock

In actual development, it is often prudent to use this " dead wait strategy". tryLock() allows us to have more choices in this situation

ReentrantLockFair locks can be implemented. The default is unfair.

But when we create an instance and pass in parameters true. It 公平锁becomes

ReentrantLock reentrantLock = new ReentrantLock(true);

synchronizeWith wait/notifythe method to realize the waiting notification of the thread, the awakened thread is random

ReentrantLockCollaborate with Conditionclasses to implement threads waiting for notifications. You can specify threads to wake up

Synchronized is a keyword in Java, and the bottom layer is implemented by JVM (C++)

ReentranLock is a class of the standard library, and the bottom layer is implemented based on Java

4. Atomic classes

The atomic class is to solve the problem of race condition (Race Condition) and data inconsistency in a multi-threaded environment. In a multi-threaded environment, if multiple threads read and write a shared variable at the same time, data inconsistency may occur, resulting in wrong results.

Atomic classes are CASimplementation-based

Java provides a variety of atomic classes. The commonly used atomic classes are as follows:

  1. AtomicInteger: Used to perform atomic operations on variables of type int.
  2. AtomicLong: Used to perform atomic operations on variables of type long.
  3. AtomicBoolean: Used to perform atomic operations on variables of type boolean.
  4. AtomicReference: Used to perform atomic operations on variables of reference type.

Next, use the atomic class AtomicIntegerto implement the operation of two threads incrementing the same variable 50,000 times.

Because it is an instance object of the class, we cannot directly perform ++ operations on the instance object of the class. We can only use some methods provided by the class

AtomicIntegerSome methods of:

AtomicInteger atomicInteger = new AtomicInteger();
// atomicInteger++
atomicInteger.getAndIncrement();
// ++atomicInteger
atomicInteger.incrementAndGet();
// atomicInteger--
atomicInteger.getAndDecrement();
// --atomicInteger
atomicInteger.decrementAndGet();
public class Demo23 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        AtomicInteger atomicInteger = new AtomicInteger();
        
        Thread t1 = new Thread(()->{
    
    
            for (int i = 0; i < 50000; i++) {
    
    
                atomicInteger.getAndIncrement();
            }
        });

        Thread t2 = new Thread(() ->{
    
    
            for (int i = 0; i < 50000; i++) {
    
    
                atomicInteger.getAndIncrement();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(atomicInteger);
    }
}

Running result:
insert image description here
If you don't use the atomic class, you need to use it synchronizedto implement it.

5. Thread pool

The thread pool has been introduced in detail in my previous article, so I won’t repeat it here. Interested friends can read this article: [Java|Multi-threading and high concurrency] Detailed explanation of thread pool

6. Signal amount

The semaphore (Semaphore) maintains a license counter indicating the number of available licenses. When a thread needs to access a shared resource, it must first obtain a permit. If the permit count is 0, the thread will be blocked until a permit is available. When a thread is done using a shared resource, it must release the license so that other threads can acquire the license and access the resource.

The number of licenses for a semaphore can be set when creating a semaphore instance

// 设置信号量的许可数量为 5
Semaphore semaphore = new Semaphore(5);

There are two main operations provided in semaphores: P (wait) and V (release).

  • P operation: It will try to obtain a semaphore license. If the license number is not 0, the license can be successfully obtained and continue to execute; if the license number is 0, the thread will be blocked until other threads release the license.

  • V operation: A semaphore license will be released, so that other blocked threads can obtain the license and continue to execute.

The method corresponding to the P operation isacquire()

The method corresponding to the V operation isrelease()

For example:

public class Demo24 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Semaphore semaphore = new Semaphore(5);
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        System.out.println("此时信号量的许可数量为0");
        semaphore.acquire();
        
        semaphore.release();
    }
}

Running results:
insert image description here
The semaphore can limit the number of threads accessing shared resources at the same time by controlling the number of permissions, thereby avoiding race conditions and data inconsistency.

7. CoutDownLatch

CountDownLatch(Countdown latch) is a synchronization tool in Java concurrent programming, which is used to wait for a group of threads to complete a certain task.

Through the construction method of CountDownLatch, specify the number of waiting threads (counter).

// 设置等待线程的数量为 5
CountDownLatch countDownLatch = new CountDownLatch(5);

When a thread has completed its task, it can call the CountDownLatch countDown()method to decrement the counter by 1. Other threads can wait for the counter to become 0 by calling the CountDownLatch await()method.

Example:

public class Demo25 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0;i < 5;i++){
    
    
            Thread t = new Thread(() ->{
    
    
                System.out.println(Thread.currentThread().getName()+" 执行任务");
                countDownLatch.countDown();
            });
            t.start();
        }

        countDownLatch.await();
    }
}

Specify the number of threads waiting for CoutDownLatch to be 5, and create 5 threads. Execute countDown()the method after the thread is executed. And call await()the waiting counter to become 0.

operation result:

insert image description here

If the initial value of the counter is greater than or equal to the number of waiting threads, it will enter the blocked waiting state.

Change the value of the counter to 6, the running result:
insert image description here

In order to avoid the above situation, you can use an overloaded version of await to set the maximum waiting time

insert image description here

8. Thread-safe collection classes

  1. Hashtable和ConcurrentHashMap: A thread-safe hash table implementation that supports highly concurrent read and write operations.

  2. CopyOnWriteArrayList: thread-safe dynamic array implementation, suitable for scenarios with more reads and fewer writes

  3. CopyOnWriteArraySet: Thread-safe collection implementation, based on CopyOnWriteArrayList, is suitable for scenarios with more reads and fewer writes.

  4. ConcurrentLinkedQueue: A thread-safe unbounded queue implementation that supports highly concurrent enqueue and dequeue operations.

  5. BlockingQueueThe implementation classes of the interface are: ArrayBlockingQueue, LinkedBlockingQueue, LinkedTransferQueueetc., which are used to implement thread-safe blocking queues.

  6. ConcurrentSkipListMap: An ordered mapping table implemented by a thread-safe jump table, which supports highly concurrent read and write operations.

  7. ConcurrentSkipListSet: An ordered collection implemented by a thread-safe jump table, supporting highly concurrent read and write operations.

For Hashtable and ConcurrentHashMap:
Hashtable is not recommended . It is a synchronizedmodified method. It is equivalent to locking this. A hash table has only one lock. There are
推荐使用ConcurrentHashMapmany optimization strategies behind this class.

  • ConcurrentHashMap locks each hash bucket.

    When two threads access the same hash bucket, there will be a conflict. If it is not the same hash bucket, there will be no lock conflict. Therefore, the probability of lock conflict is greatly reduced

  • ConcurrentHashMap only locks write operations, but does not lock read operations.

    When multiple threads are writing at the same time, there will be lock conflicts, and there will be no lock conflicts when reading operations at the same time. When some threads are writing, some threads are reading. There is no thread safety problem. ConcurrentHashMap guarantees the read data It will not be half-written, either before or after.

  • ConcurrentHashMap makes full use of the characteristics of CAS. There are many places where CAS is used internally instead of directly locking

  • ConcurrentHashMap has specially optimized the expansion operation.

    During the expansion process, the old hash table and the new hash table will exist for a period of time at the same time. Every time the operation of the hash table is performed, a part of the elements in the old hash table will be moved until the transfer is completed. Avoid The expansion time is too long, resulting in the situation of Caton

The difference between HashMap, Hashtable and ConcurrentHashMap is also a common interview question.

Answer this question. In terms of thread safety, HashMap is thread-unsafe. Hashtable and ConcurrentHashMap are thread-safe, and then answer the difference between Hashtable and ConcurrentHashMap. What improvements have ConcurrentHashMap made compared to Hashtable, etc.

CopyOnWriteArrayList is suitable for scenarios that read more and write less.
Under normal circumstances, if some threads are writing (modifying), the advantage thread is reading, and it is likely to read half of the modified data. Therefore, in order to solve this problem, CopyOnWriteArrayList will Make a copy of the original data, and the write operation will be performed on the copied data

But if there is a lot of data/modification is very frequent, it is not suitable for use

Thank you for watching! I hope this article can help you!
Column: "Java Learning Journey from Scratch" is constantly being updated, welcome to subscribe!
"Wish to encourage you and work together!"

insert image description here

Guess you like

Origin blog.csdn.net/m0_63463510/article/details/131491870