[JavaEE] JUC (Java.util.concurrent) common classes

Insert image description here

Preface

After studying the previous article, we have a general understanding of how to implement multi-threaded programming and solve the thread insecurity problems encountered in multi-threaded programming. java.util.concurrentIt is a common package for our multi-threaded programming. So today I will share with you java.util.concurrentseveral other packages under the package. A common category.

ReentrantLock

ReentrantLock is a reentrant mutex lock. It is similar to synchronized positioning and is used to ensure thread safety. However, ReentrantLock has outstanding advantages over synchronized in some aspects.

There are two locking methods of ReentrantLock:

  1. lock(): If the lock cannot be obtained, the thread will enter the blocking waiting state.
  2. tryLock(): If the lock cannot be obtained, it will give up the lock instead of entering the blocking waiting state.

When using ReentrantLock, you need to manually unLock() to unlock. If you forget to manually unlock this operation, it will bring serious consequences.

Therefore, in order to solve the problem of forgetting to unlock manually sometimes, it is often necessary to use try-finally to perform the unlocking operation.

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    
    
    //working
}finally {
    
    
    lock.unlock();
}

Advantages of ReentranLock compared to synchronized

  1. ReentrantLock has two methods when locking, lock() and tryLock(). When using lock(), if the lock is not acquired, it will enter the blocking waiting state. When using tryLock(), if the lock is not acquired, it will give up the acquisition instead of entering the blocking waiting state.

  2. ReentrantLock provides an implementation of fair lock (unfair lock by default)

  3. ReentranLock provides a more powerful waiting notification mechanism. It is paired with the Condition class to implement waiting notifications and can specify to wake up a certain thread.

How to choose which lock to use?

  • When lock competition is not fierce, using synchronized is more efficient and automatic release is more convenient.
  • When lock competition is fierce, use ReentrantLock, combined with trylock, to more flexibly control locking behavior instead of waiting to death.
  • If you need to use a fair lock, use ReentrantLock.

Under normal circumstances, it is recommended to use synchronized. Although ReentrantLock has advantages in some aspects, it is more troublesome to use, and Java programmers have made a lot of optimizations in synchronized.

Atomic class

Atomic classes are internally implemented using CAS operations. Because CAS operations are atomic and do not require locking operations, the performance is much better than locking.

  • AtomicBoolean
  • AtomicInteger
  • AtomicIntegerArray
  • AtomicLong
  • AtomicReference
  • AtomicStampedReference

Common methods of atomic classes:

  • addAndGet(int delta); —— i += delta;
  • decrementAndGet(); —— --i;
  • getAndDecrement(); —— i–;
  • incrementAndGet(); —— ++i;
  • getAndIncrement(); —— i++;

I have explained the CAS operation in detail before. If you want to know more, you can check it out. CAS (Compare And Swap) operation

Thread Pool

The thread pool is to solve the problem of resource waste caused by frequent creation and destruction of threads.

Using the thread pool requires the use of two classes ExecutorService: and .Executors

ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
    
    
	@Override
	public void run() {
    
    
		System.out.println("hello");
	}
});
  • ExecutorService represents a thread pool instance.
  • Executors is a factory class that can create several different styles of thread pools.
  • The submit method of ExecutorService can submit several tasks to the thread pool

Several ways for Executors to create thread pools

  • newFixedThreadPool: Create a thread pool with a fixed number of threads
  • newCachedThreadPool: Create a thread pool with a dynamically growing number of threads.
  • newSingleThreadExecutor: Create a thread pool containing only a single thread.
  • newScheduledThreadPool: Execute the command after setting the delay time, or execute the command regularly. It is an advanced version of Timer.

This also involves the factory mode. You can read this article to learn about [JavaEE] multi-threading case-thread pool.

Signal amount

Semaphore is a mechanism that implements communication between tasks. It can be used to achieve synchronization between tasks or mutually exclusive access to critical resources. It is often used to assist a set of competing tasks to access critical resources.

A semaphore is a non-negative integer, and the task that obtains the semaphore will decrement the integer by 1. When the semaphore is 0, all tasks trying to obtain the semaphore will be blocked. The value of the semaphore represents the accumulated number of operations to release the semaphore.

The operation of applying for resources is called P operation, and the operation of releasing resources is called V operation.

Semaphores can be divided into two types: binary semaphores and counting semaphores. A binary semaphore has only one message queue, and the queue has two states: empty or full. The counting semaphore can be regarded as a message queue with a length greater than 1 and is used for counting. The semaphore count indicates how many events have yet to be processed. When an event occurs, the task or interrupt will release a semaphore (increment the semaphore count value by 1); when an event needs to be processed, the task or interrupt will take away a semaphore (decrement the semaphore count value by 1). ).

The locking and unlocking operations can be regarded as binary semaphore operations. When the lock is locked, the semaphore is 0, and when the lock is released, the semaphore is 1.

In Java code, semaphore-related operations are encapsulated in Semaphoreclasses. acquire()Methods represent applying for resources, and release()methods represent releasing resources.

public class Test2 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Semaphore semaphore = new Semaphore(4);
        semaphore.acquire();
        System.out.println("获取资源");
        semaphore.acquire();
        System.out.println("获取资源");
        semaphore.acquire();
        System.out.println("获取资源");
        semaphore.acquire();
        System.out.println("获取资源");
        semaphore.acquire();
        System.out.println("获取资源");
        semaphore.release();
        System.out.println("释放资源");
    }
}

Insert image description here

When the amount of resources requested is greater than the total amount of resources, the thread will enter the blocking waiting state until other threads release part of the semaphore.

CountDownLatch

CountDownLatch is a synchronization tool class in Java, used to coordinate synchronization between multiple threads. It allows one or more threads to wait until a set of operations performed in other threads is completed.

CountDownLatch implements this function through a counter. The initial value of the counter is usually set to the number of threads that need to wait for completion. Whenever a thread completes its task, the counter value is decremented by 1. When the counter value reaches 0, it means that all threads have completed their tasks, and the threads waiting on CountDownLatch can resume execution.

CountDownLatch can be used to ensure that certain activities do not continue until other activities have completed. For example, you can ensure that a computation does not continue until all the resources it requires have been initialized, or that a service does not start until all other services it depends on have started.

In Java, you can use the countDown() method of CountDownLatch to decrement the counter, which is to tell CountDownLatch that the current task is completed, and use the await() method to wait for the counter to reach 0. All threads calling the await() method will be blocked until the counter reaches 0 or the waiting thread is interrupted or times out.

public class Demo2 {
    
    
    private static int count;
    public static void main(String[] args) throws InterruptedException {
    
    
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for(int i = 0; i < 10; i++) {
    
    
            Thread t = new Thread(() -> {
    
    
                try {
    
    
                    Thread.sleep((long)(Math.random() * 4000));
                    System.out.println(++count + "号完成比赛");
                    countDownLatch.countDown();
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
        countDownLatch.await();
        System.out.println("结束比赛");
    }
}

Insert image description here
Only after calling countDown()the method 10 times await()will the method end waiting and continue executing the following code.

It should be noted that CountDownLatch is one-time use. Once the counter value reaches 0, it cannot be used again. If you need to use similar functions multiple times, you can consider using other synchronization tool classes such as CyclicBarrier.

Related interview questions

1) What are the methods of thread synchronization?

synchronized, ReentrantLock, Semaphore, etc. can all be used for thread synchronization.

2) Why do we need the lock under juc when we have synchronized?

Taking juc's ReentrantLock as an example,

  • There is no need to manually release the lock when using synchronized. You need to manually release the lock when using ReentrantLock. It is more flexible to use.
  • When synchronized fails to apply for a lock, it will wait. ReentrantLock can wait for a period of time through trylock and then give up.
  • synchronized is an unfair lock, and ReentrantLock is an unfair lock by default. You can pass a true through the construction method to turn on the fair lock mode.
  • synchronized implements wait-wake-up through Object's wait/notify. Each time it wakes up a randomly waiting thread. ReentrantLock is used with the Condition class to implement wait-wake-up, which can more accurately control the wake-up of a specified thread.

3) What is the implementation principle of AtomicInteger?

Based on CAS mechanism. The pseudo code is as follows:

class AtomicInteger {
    
    
	private int value;
	public int getAndIncrement() {
    
    
		int oldValue = value;
		while ( CAS(value, oldValue, oldValue+1) != true) {
    
    
			oldValue = value;
		}
		return oldValue;
	}
}

4) Have you heard of semaphore? In what scenarios has it been used before?

Semaphore is used to represent "the number of available resources". It is essentially a counter.

You can use semaphores to implement "shared locks". For example, if a resource allows three threads to use it at the same time, you can use the P operation as locking and the V operation as unlocking. The P operations of the first three threads can all return smoothly, and subsequent threads can Any further P operations will block and wait until the previous thread performs the V operation.

Guess you like

Origin blog.csdn.net/m0_73888323/article/details/133488166