Diretório do artigo
Condição implementa as variáveis de condição no modelo de gerenciamento.
Há apenas uma variável de condição no programa Java interno, e o programa de controle implementado pelo Lock & Condition suporta várias variáveis de condição, o que é uma diferença importante entre as duas.
1. Como usar duas variáveis de condição para implementar rapidamente uma fila de bloqueio?
Uma fila de bloqueio requer duas variáveis de condição, uma é que a fila não esteja vazia (a fila vazia não pode sair da fila) e a outra é que a fila não está cheia (a fila está cheia e a fila não é permitida).
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();
}
}
}
Os procedimentos implementados de bloqueio e condição, espera e notificação de encadeamento, podem apenas chamar a espera (), sinal (), sinalAll (), suas semânticas e espera (), notify (), notifyAll () usadas pelo sincronizado para alcançar o processo de gerenciamento. Da mesma maneira, os métodos usados para implementar os dois procedimentos não podem ser confundidos.
2. Síncrono e Assíncrono
Em termos leigos, é se o chamador precisa esperar pelo resultado, se necessário, é síncrono; se não, é assíncrono.
Dois métodos para obter assíncrono:
- O chamador cria um sub-thread e executa a chamada de método no sub-thread, chamado de chamada assíncrona;
- Quando o método é implementado, um novo encadeamento é criado para executar a lógica principal, e o encadeamento principal retorna diretamente, chamado método assíncrono.
3. Análise de código fonte do Dubbo
O protocolo TCP em si é assíncrono.No nível do protocolo TCP, após o envio de uma solicitação RPC, o thread não aguardará o resultado da resposta do RPC.
Dubbo fez algo assíncrono para síncrono.
Para a seguinte chamada RPC simples, o método sayHello () é um método síncrono por padrão, ou seja, quando service.sayHello ("dubbo") é executado, o encadeamento pára e aguarda o resultado.
DemoService service = 初始化部分省略
String message = service.sayHello("dubbo");
System.out.println(message);
O encadeamento de despejo saiu e descobriu que o encadeamento estava bloqueado e o status era TIMED_WAITING. Originalmente, a solicitação era enviada de forma assíncrona, mas o encadeamento de chamada estava bloqueado, indicando que o Dubbo fazia a coisa assíncrona para síncrona. O encadeamento está bloqueado no método DefaultFuture.get ().
A linha 108 do DubboInvoker chama DefaultFuture.get (). Essa linha primeiro chama o método request (inv, timeout) .Este método está realmente enviando uma solicitação RPC e aguardando o resultado do retorno RPC chamando o método get ().
public class DubboInvoker{
Result doInvoke(Invocation inv){
// 下面这行就是源码中 108 行
// 为了便于展示,做了修改
return currentClient
.request(inv, timeout)
.get();
}
}
O código relevante é simplificado, repetindo nossos requisitos: quando o RPC retornar o resultado, bloqueie o thread de chamada e deixe o thread de chamada aguardar; quando o RPC retornar o resultado, ative o thread de chamada e deixe o thread de chamada executar novamente. Mecanismo clássico de notificação de espera. A seguir está a implementação do Dubbo
// 创建锁与条件变量
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();
}
}
O encadeamento de chamada aguarda o RPC retornar o resultado chamando o método get (). Nesse método, tudo o que você vê são "rostos" familiares: bloqueio de chamada () para adquirir o bloqueio e, finalmente, desbloquear () para liberar o bloqueio; após adquirir o bloqueio, A espera é obtida chamando o método waitit () em um loop clássico.
Quando o resultado da RPC retornar, ele chamará o método doReceived (). Nesse método, chame lock () para adquirir o bloqueio e, finalmente, chame unlock () para liberar o bloqueio. Após adquirir o bloqueio, chame o sinal () para notificar o thread de chamada que o resultado retornou. , Não precisa mais esperar.