- Wait and notify
- condition variable
- countdown coordinator
- fence
- blocking queue
- Flow Control and Semaphores
- Exchanger
- How to properly stop a thread
Wait and notify
On the java platform, you can achieve thread waiting and notification by using Object.wait()/Object.wait(long) and Object.notify()/Object.notifyAll().
Object.wait() can suspend the current thread (the state is changed to WAITING). This method can be used to implement waiting. The thread where it is located is called the waiting thread.
Object.notify() can wake up a waiting thread, which is called the notification thread.
Both wait() and notify() methods are methods of the Object class, because it is the parent class of all classes, so all classes have these two methods.
synchronized(lockObject){ while(等待条件){ lockObject.wait(); } ...... //后续操作 }
In the above code, the judgment condition of while is called the waiting condition for the time being. When this condition is established, the thread will enter the waiting condition. When other threads wake it up again, it will judge again whether the waiting condition is established. If it is not established, the thread can Continue to perform the corresponding operations, otherwise continue to enter the waiting state.
Let's think about the role of while and why wait conditions should be used with while instead of if.
/** * @ClassName WaitIfSample * @description: * @author: yong.yuan * @create: 2022-04-15 16:44 * @Version 1.0 **/ public class WaitIfExample { static AtomicInteger stock = new AtomicInteger(); static final Object LOCKER = new Object(); static class Consumer implements Runnable{ @Override public void run() { consume(); } void consume(){ synchronized (LOCKER){ while (stock.get() == 0) { try { LOCKER.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } stock.getAndDecrement(); System.out.println("consumer " + Thread.currentThread().getName() + "消费消息后库存为:"+stock.get()); LOCKER.notifyAll(); } } } static class Producer implements Runnable{ @Override public void run() { product(); } void product(){ synchronized (LOCKER) { while (stock.get() != 0) { try { LOCKER.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } stock.getAndAdd(2); System.out.println("producer 生产消息,生产后库存为:"+stock.get()); LOCKER.notifyAll(); } } } public static void main(String[] args) { Consumer consumer = new Consumer(); new Thread(consumer).start(); new Thread(consumer).start(); new Thread(consumer).start(); new Thread(new Producer()).start(); } }
Above we implement a simple producer-consumer message queue. For consumers, consumption can only be done when the inventory is greater than 0. Suppose there are 3 consumer threads currently executing, but the inventory is 0 during initialization. , so these 3 threads all enter the WAITING state, and then a producer adds 2 to the inventory, and then wakes up all waiting threads. The awakened thread will first judge whether the waiting condition is true or not through while, and it is established → continue Wait, not established → execute the consumption action backwards, 2 of the 3 waiting threads in the code will consume successfully, and 1 will continue to enter the waiting state.
producer 生产消息,生产后库存为:2 consumer Thread-2消费消息后库存为:1 consumer Thread-1消费消息后库存为:0
But what if you replace the while with an if? When the waiting thread is awakened, even if the waiting condition is established, the thread will continue to execute, which is obviously not the result we want.
producer 生产消息,生产后库存为:2 consumer Thread-2消费消息后库存为:1 consumer Thread-1消费消息后库存为:0 consumer Thread-0消费消息后库存为:-1
Of course, while with waiting conditions is only a general scenario, and some special scenarios do not use while or use if.
- Object.wait(long) allows us to specify a timeout (in milliseconds). If the waiting thread is not woken up by other threads within this time, the java virtual machine will automatically wake up the thread, but this will neither throw an exception nor return value, so whether the thread wakes up automatically requires some extra action.
The overhead and problems of wait/notify
- Premature wake-up: The three threads A, B, and C all use the same lock object, and there is a judgment of thread suspension, but the judgment conditions for thread suspension used by A and B are different from those of C, so when A, B , When C is in the WAITING state at the same time, a thread will use notifyAll() to wake up A and B, which will also wake up C at the same time, but C continues to enter the suspended state through the while judgment, that is to say, the notify action is Not much related to C, this is called premature wakeup.
- Signal loss: If the waiting thread does not first determine whether the protection condition has been established before executing Object.wait(), then this may happen - the notification thread has updated the relevant shared variable before the waiting thread enters the critical section, The corresponding protection conditions are established and notified, but at this time the waiting thread has not been suspended, and naturally it does not matter to wake up. This may cause the waiting thread to be suspended by directly executing Object.wait(), and the thread has been in a waiting state because there is no other thread to notify. This phenomenon is equivalent to the waiting thread missing a "signal" that was "sent" to it, so it is called signal loss.
- Fraudulent wakeup: A thread may wake up without another thread doing notify/notifyAll, this is called a fraudulent wakeup. This problem can be solved by adding while() outside the wait() method to judge.
- Context switching: wait/notify corresponds to thread suspension / thread wakeup , so it will result in multiple application and release of locks, and the application and release of locks may cause context switching.
- The java virtual machine maintains a queue called a wait set for each object, which is used to store the waiting threads on the object. Object.wait() will make the current thread suspend and release the corresponding lock, and Store the current thread in the object's wait set. Executing Object.notify() will wake up any thread in the waiting set of the object. The awakened thread will not be removed from the waiting set immediately, but will not be removed until the thread holds the object lock for the second time. will be removed.
Thread.join(): After a thread is executed, this thread can execute
static void main(){ Thread t = new Thread(); t.start(); ...... t.join();//A ...... }
As an example above, the main thread can only execute the content behind A after the execution of thread t is completed.
condition variable
- Condition can be used as an alternative to wait/notify to implement waiting/notification. Its await(), signal()/signalAll() correspond to wait(), notify()/notifyAll() respectively, and solve premature wakeup and wait( long) whether the timeout cannot be judged, etc.
Object.wait()/Object.notify() requires the thread of execution to hold an internal lock on the object.
Condition.await()/Condition.signal() requires the thread of execution to hold an explicit lock on the object.
- Condition.awaitUntil(Date), the parameter is the waiting deadline, this method will return true when awaitUntil is signaled by other threads.
Condition usage example
/** * @ClassName ConditionSimple * @description: * @author: yong.yuan * @create: 2022-04-18 10:40 * @Version 1.0 **/ public class ConditionExample { static Lock lock = new ReentrantLock(); static Condition conditionA = lock.newCondition(); static Condition conditionB = lock.newCondition(); static BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3); static class Consumer implements Runnable{ @Override public void run() { lock.lock(); try { while (queue.isEmpty()){ System.out.println("消费者暂停中...."); conditionA.await(); } System.out.println("消费线程"+Thread.currentThread().getName() +"消费消息:"+queue.take()); conditionB.signalAll(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } } static class Producer implements Runnable{ @Override public void run() { lock.lock(); try { while (queue.remainingCapacity() == 0){ System.out.println("生产者暂停中..."); conditionB.await(); } System.out.println("生产线程"+Thread.currentThread().getName() +"生产消息..."); queue.add("hello"); conditionA.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } }
countdown coordinator
CountDownLatch is generally used to realize that another thread waits for other threads to complete a set of specific operations before continuing to execute. This set of specific operations is called a prerequisite operation.
CountDownLatch will have a counter that maintains the number of unfinished prerequisite operations countDownLatch.countDown() is executed once, the counter will be -1, CountDownLatch.await() is equivalent to a protected method, when its thread executes this method After that, the thread will be suspended until the counter maintained by its internal lock is 0, the thread will be awakened and continue to execute downward.
When the counter in CountDownLatch is 0, the countDown method is executed again, the value of the counter will not change, and no exception will be thrown, and the thread will not stop if the await method is executed again, which means that the use of CountDownLatch is one-time.
When using CountDownLatch to achieve waiting/notification, there is no need to lock when calling the await and countDown methods.
Usage scenario: For example, when starting a java service, there is a calling relationship between each service. Generally, a corresponding number of called services are started first. For example, after A service starts 5 instances, B service can be started, then there can be a CountDownLatch with an initial value of 5. Set await before B service starts, and each A service starts countDown. When A service instance is started, B just started.
public class CountDownLatchExample {
private static final CountDownLatch latch = new CountDownLatch(4);
private static int data;
public static void main(String[] args) throws InterruptedException {
Thread workerThread = new Thread() {
@Override
public void run() {
for (int i = 1; i < 10; i++) {
data = i;
latch.countDown();
// 使当前线程暂停(随机)一段时间
Tools.randomPause(1000);
}
};
};
workerThread.start();
latch.await();
Debug.info("It's done. data=%d", data);
}
}
fence
CyclicBarrier and CountDownLatch have certain similarities. CountDownLatch is an await thread that waits for other threads to complete a certain number of prerequisites before continuing to execute. CyclicBarrier will set an await point in multiple threads, and the number of threads that reach this point has reached the set point. quantity requirements will continue to be implemented.
/**
* @ClassName ClimbMountains
* @description:
* @author: yong.yuan
* @create: 2022-04-03 21:59
* @Version 1.0
**/
public class ClimbMountains {
static Logger logger = Logger.getLogger(ClimbMountains.class.getName());
static CyclicBarrier[] climbBarrier = new CyclicBarrier[2];
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
Company company = new Company(i);
company.start();
}
}
static class Company{
private final String name;
private final List<Staff> staffList;
public Company(int c) {
this.name ="公司".concat(String.valueOf(c));
climbBarrier[c] = new CyclicBarrier(5);
staffList = new ArrayList<>();
for (int j = 0; j < 5; j++) {
staffList.add(new Staff(name,c,j));
}
}
synchronized void start() {
String log = String.format("%s 五位精英开始攀登....",name);
logger.info(log);
for (Staff staff:staffList) {
new Thread(staff::start).start();
}
}
}
static class Staff{
private final String name;
private final int c;
public Staff(String company,int c,int s) {
this.c = c;
this.name = company.concat("-员工").concat(String.valueOf(s));
}
void start() {
System.out.println(name + ":开始攀登!");
try {
int time = new Random().nextInt(20000);
Thread.sleep(time);
System.out.println(name+":用时"+time/1000+"秒");
climbBarrier[c].await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}finally {
System.out.println(name + ":完毕");
}
}
}
}
=================输出结果=================
四月 18, 2022 4:00:49 下午 io.github.viscent.mtia.ch5.ClimbMountains$Company start
信息: 公司0 五位精英开始攀登....
公司0-员工0:开始攀登!
公司0-员工1:开始攀登!
公司0-员工2:开始攀登!
公司0-员工3:开始攀登!
公司0-员工4:开始攀登!
公司1-员工0:开始攀登!
公司1-员工1:开始攀登!
公司1-员工2:开始攀登!
公司1-员工3:开始攀登!
公司1-员工4:开始攀登!
四月 18, 2022 4:00:49 下午 io.github.viscent.mtia.ch5.ClimbMountains$Company start
信息: 公司1 五位精英开始攀登....
公司1-员工3:用时4秒
公司1-员工4:用时4秒
公司0-员工0:用时5秒
公司0-员工3:用时7秒
公司0-员工2:用时9秒
公司1-员工2:用时11秒
公司1-员工1:用时12秒
公司1-员工0:用时13秒
公司1-员工0:完毕
公司1-员工1:完毕
公司1-员工2:完毕
公司1-员工4:完毕
公司1-员工3:完毕
公司0-员工4:用时13秒
公司0-员工1:用时16秒
公司0-员工0:完毕
公司0-员工4:完毕
公司0-员工2:完毕
公司0-员工3:完毕

公司0-员工1:完毕
blocking queue
Blocking queues can be divided into bounded queues and unbounded queues according to whether their storage space is limited. The queue capacity of bounded queues is set by the program. The capacity of unbounded queues is Integer.MAX\_VALUE, which is 2^31
Common blocking queues and common methods
ArrayBlockingQueue
Its underlying data structure is an array, so it will not cause the burden of garbage collection when put and take, it uses the same explicit lock when put and take, so it causes him to put and take when It will be accompanied by the release and application of locks. If a large number of threads are constantly putting and taking, the lock competition will be too high, which will continuously lead to context switching.
LinkedBlockingQueue
The underlying data structure is a linked list, so it will be accompanied by dynamic allocation of space when putting a take, that is, each put or take operation will be accompanied by the creation and removal of nodes, which will cause garbage collection. burden. However, its put and take operations use two different explicit locks, which will relatively slow down the lock competition.
In addition, it maintains an Atomic variable internally to maintain the queue length, and there may also be constant contention between the put thread and the take thread.
SychronousQueue
The capacity is 0, which is mainly used to forward tasks (blocking effect). When SychronousQueue.take(E), no thread executes SychronousQueue.put(E), then the consuming thread will suspend until there is a production thread executing SychronousQueue.put(E),
Similarly, when SychronousQueue.put(E), no consuming thread executes SychronousQueue.take(E), and the production thread will stop until a consuming thread executes SychronousQueue.take(E).
The former of SychronousQueue and ArrayBlockingQueue/LinkedBlockingQueue is like when the courier delivers the courier to you to deliver the next courier, while the latter is to put the courier directly in the honeycomb locker.
PriorityBlockingQueue
The unbounded blocking queue that supports priority can be implemented by a custom class to implement the compareTo method to specify the ordering rules of elements. It will block if the queue is empty when taking, but it will expand infinitely, so put will not block. Its implementation principle is heap sort
Insert another element 4 into the minimum heap [1, 5, 8, 6, 10, 11, 20]. The following diagram analyzes the insertion process:
After removing elements from the max heap [20, 10, 15, 6, 9, 10, 12], the following diagram analyzes the rearrangement process:
DelayWorkQueue
DelayWorkQueue is a PriorityBlockingQueue that implements the delay function
The internal time heap structure is used (it will be sorted when inserted), which is characterized by the fact that the internal elements will not be dequeued in the order in which they were enqueued.
Instead, the internal elements are sorted according to the length of the delay.
Both ArrayBlockingQueue and SynchronousQueue support both unfair and fair scheduling, while LinkedBlockingQueue only supports unfair scheduling.
If the degree of concurrency between the producer and consumer threads is high, the likelihood of these threads contending for the locks used inside the transmission channel also increases. At this time, the implementation of bounded queue is suitable for LinkedBlockingQueue, otherwise we can consider ArrayBlockingQueue.
- LinkedBlockingQueue is suitable for use when the degree of concurrency between producer threads and consumer threads is relatively large
- ArrayBlockingQueue is suitable for use when the degree of concurrency between producer threads and consumer threads is low
- SynchronousQueue is suitable for use when the processing capacity of consumers is not much different from that of producers.
Flow Control and Semaphores
SemaPhore is usually called a semaphore, which is generally used to control the number of threads accessing a specific resource at the same time, and to ensure the rational use of resources by coordinating each thread.
The commonly used scenario is to limit the current, for example, to limit the number of connection threads in the database connection pool.
Common method
acquire()
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
acquire(int permits)
获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
acquireUninterruptibly()
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
hasQueuedThreads()
等待队列里是否还存在等待线程。
getQueueLength()
获取等待队列里阻塞的线程数。
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits()
返回可用的令牌数量。
An example of implementing a simple token acquisition
/**
* @ClassName SemaphoreExample
* @description:
* @author: yong.yuan
* @create: 2022-04-18 18:59
* @Version 1.0
**/
public class SemaphoreExample {
static final Semaphore semaphore = new Semaphore(5);
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static CountDownLatch countDownLatch = new CountDownLatch(10);
static class Worker implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
semaphore.acquire();
System.out.println(Thread.currentThread().getId()+
"号工人,从流水线上取货物一件,现有" + semaphore.availablePermits() + "件货物");
countDownLatch.countDown();
lock.lock();
try {
condition.signalAll();
}finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Machine implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
while(semaphore.availablePermits() >= 5) {
lock.lock();
try {
System.out.println("流水线上的货物满了");
condition.await();
} finally {
lock.unlock();
}
}
semaphore.release();
System.out.println("向流水线上送货物,现有"+semaphore.availablePermits()
+"件货物");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(new Worker()).start();
new Thread(new Machine()).start();
}
countDownLatch.await();
System.out.println("已经有10个工人搬走货物了");
while (semaphore.tryAcquire()){
System.out.println("剩余货物搬走.....");
}
}
}
==============执行结果==============
流水线上的货物满了
流水线上的货物满了
17号工人,从流水线上取货物一件,现有4件货物
向流水线上送货物,现有4件货物
流水线上的货物满了
向流水线上送货物,现有5件货物
19号工人,从流水线上取货物一件,现有5件货物
13号工人,从流水线上取货物一件,现有4件货物
向流水线上送货物,现有5件货物
流水线上的货物满了
向流水线上送货物,现有4件货物
27号工人,从流水线上取货物一件,现有3件货物
15号工人,从流水线上取货物一件,现有4件货物
向流水线上送货物,现有5件货物
向流水线上送货物,现有5件货物
25号工人,从流水线上取货物一件,现有4件货物
21号工人,从流水线上取货物一件,现有4件货物
向流水线上送货物,现有5件货物
23号工人,从流水线上取货物一件,现有4件货物
向流水线上送货物,现有5件货物
流水线上的货物满了
向流水线上送货物,现有5件货物
29号工人,从流水线上取货物一件,现有4件货物
向流水线上送货物,现有5件货物
31号工人,从流水线上取货物一件,现有5件货物
已经有10个工人搬走货物了
剩余货物搬走.....
剩余货物搬走.....
剩余货物搬走.....
剩余货物搬走.....
剩余货物搬走.....
Exchanger
The Exchanger class can be used to exchange information between two threads. The Exchanger object can be simply understood as a container containing two grids, and information can be filled into the two grids through the exchanger method. When the two grids are filled, the object will automatically exchange the information of the two grids, and then return to the thread, thus realizing the information exchange between the two threads.
When a consumer thread consumes a filled buffer, another buffer can be filled by the producer thread, thus realizing concurrent data generation and consumption. This buffering technique is called double buffering
/**
* @ClassName ExchangerExample
* @description:
* @author: yong.yuan
* @create: 2022-04-18 23:10
* @Version 1.0
**/
public class ExchangerExample {
static Exchanger<String> STRING_EXCHANGER = new Exchanger<>();
static class MyThread extends Thread{
String msg;
public MyThread(String threadName,String msg) {
Thread.currentThread().setName(threadName);
this.msg = msg;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+":"
+STRING_EXCHANGER.exchange(msg));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new MyThread("thread-1","hello thread-1");
Thread t2 = new MyThread("thread-2","hello thread-2");
t1.start();
t2.start();
}
}
===============操作结果===============
Thread-1:hello thread-1
Thread-0:hello thread-2
How to properly stop a thread
Terminate a thread with interrupt
- Using interrupt is actually to stop the thread through the change of the interrupt state, without immediately terminating the thread.
- When the thread is in the wait() or sleep() state, using interrupt can wake up the sleeping thread, but an exception will be thrown. We can manually use interrupt() in Catch(InterruptedExcetion e){} to terminate the thread
- Compared with other termination methods - stop(): it will directly stop the thread and cannot process the data that you want to process before stopping
public class StopThread{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000){
System.out.println("count = " + count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//线程在休眠期间被中断,那么会自动清除中断信号,所以需要在catch中再次中断
Thread.currentThread().interrupt();
}
}
});
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。