15. Lock and Condition (below): How does Dubbo use the pipe process to achieve asynchronous transfer synchronization? -Concurrency tools


Condition implements the condition variables in the management model.

There is only one condition variable in the built-in Java program, and the control program implemented by Lock & Condition supports multiple condition variables, which is an important difference between the two.

1. How to use two condition variables to quickly implement a blocking queue?

A blocking queue requires two condition variables, one is that the queue is not empty (the empty queue is not allowed to leave the queue), and the other is the queue is not full (the queue is full and the queue is not allowed).

public class BlockedQueue<T> {
	final Lock lock = new ReentrantLock();
	// 条件变量:队列不满
	final Condition notFull = lock.newCondition();
	// 条件变量:队列不空
	final Condition notEmpty = lock.newCondition();

	// 入队
	void enq(T x) {
		lock.lock();
		try {
			while (队列已满) {
				// 等待队列不满
				notFull.await();
			}
			// 省略入队操作...
			// 入队后, 通知可出队
			notEmpty.signal();
		} finally {
			lock.unlock();
		}
	}

	// 出队
	void deq() {
		lock.lock();
		try {
			while (队列已空) {
				// 等待队列不空
				notEmpty.await();
			}
			// 省略出队操作...
			// 出队后,通知可入队
			notFull.signal();
		} finally {
			lock.unlock();
		}
	}
}

Lock and Condition implemented procedures, thread waiting and notification can only call await (), signal (), signalAll (), their semantics and wait (), notify (), notifyAll () used by synchronized to achieve the management process are In the same way, the methods used to implement the two procedures cannot be mistaken.

2. Synchronous and asynchronous

In layman's terms, it is whether the caller needs to wait for the result, if necessary, it is synchronous; if not, it is asynchronous.

Two methods to achieve asynchronous:

  • The caller creates a sub-thread, and executes the method call in the sub-thread, which is called asynchronous call;
  • When the method is implemented, a new thread is created to execute the main logic, and the main thread directly returns, called the asynchronous method.

3. Dubbo source code analysis

The TCP protocol itself is asynchronous. At the TCP protocol level, after sending an RPC request, the thread will not wait for the response result of the RPC.

Dubbo did something asynchronous to synchronous.
For the following simple RPC call, the sayHello () method is a synchronous method by default, that is, when service.sayHello ("dubbo") is executed, the thread will stop and wait for the result.

DemoService service = 初始化部分省略
String message = service.sayHello("dubbo");
System.out.println(message);

The dump thread came out and found that the thread was blocked and the status was TIMED_WAITING. Originally the request was sent asynchronously, but the calling thread was blocked, indicating that Dubbo did the asynchronous to synchronous thing. The thread is blocked in the DefaultFuture.get () method.
Insert picture description here
DubboInvoker's line 108 calls DefaultFuture.get (). This line first calls the request (inv, timeout) method. This method is actually sending an RPC request, and then waiting for the RPC return result by calling the get () method.

public class DubboInvoker{
  Result doInvoke(Invocation inv){
    // 下面这行就是源码中 108 行
    // 为了便于展示,做了修改
    return currentClient 
      .request(inv, timeout)
      .get();
  }
}

Relevant code is simplified, repeating our requirements: when the RPC returns the result, block the calling thread and let the calling thread wait; when the RPC returns the result, wake up the calling thread and let the calling thread execute again. Classic wait-notification mechanism. The following is the Dubbo implementation

// 创建锁与条件变量
	private final Lock lock = new ReentrantLock();
	private final Condition done = lock.newCondition();

	// 调用方通过该方法等待结果
	Object get(int timeout) {
		long start = System.nanoTime();
		lock.lock();
		try {
			while (!isDone()) {
				done.await(timeout);
				long cur = System.nanoTime();
				if (isDone() || cur - start > timeout) {
					break;
				}
			}
		} finally {
			lock.unlock();
		}
		if (!isDone()) {
			throw new TimeoutException();
		}
		return returnFromResponse();
	}

	// RPC 结果是否已经返回
	boolean isDone() {
		return response != null;
	}

	// RPC 结果返回时调用该方法
	private void doReceived(Response res) {
		lock.lock();
		try {
			response = res;
			if (done != null) {
				done.signal();
			}
		} finally {
			lock.unlock();
		}
	}

The calling thread waits for the RPC to return the result by calling the get () method. In this method, all you see are familiar “faces”: call lock () to acquire the lock, and finally unlock () to release the lock; after acquiring the lock, Waiting is achieved by calling the await () method in a classic loop.

When the RPC result returns, it will call the doReceived () method. In this method, call lock () to acquire the lock, and finally call unlock () to release the lock. After acquiring the lock, call the signal () to notify the calling thread that the result has returned , No longer have to wait.

Published 97 original articles · praised 3 · 10,000+ views

Guess you like

Origin blog.csdn.net/qq_39530821/article/details/102536337