【Java并发】【AQS锁】锁在源码中的应用
1 前言
本节主要记录下基于 AQS 衍生出来的一些常用锁比如:CountDownLatch、ReentrantLock、Semaphore、ReentrantReadWriteLock 等他们在源码中的一些应用,好记性不如烂笔头。
2 CountDownLatch
2.1 RocketMQ 中 Broker 向 所有NameServer 的注册
RocketMQ 路由注册是通过 Broker 与 NameServer 的心跳功能实现的。 Broker启动时向集群中所有的NameServ巳r发送心跳语句,每隔 30s 向集群中所有NameServer发送心跳包, NameServer 收到 Broker 心跳包时会更新 brokerLiveTable 缓存中 BrokerLivelnfo 的 lastUpdateTimestamp ,然后 Nam巳 Server 每隔 10s 扫描 brokerLiveTable,如果连续 !20s 没有收到心跳包, NameServer将移除该Broker 的路由信息同时关闭 Socket连接。
那么这里比如我有 4个 NameServer,是循环遍历一个一个注册么?其实不是,这里就用到了线程池 + CountDownLatch,我们看下:
public List<RegisterBrokerResult> registerBrokerAll( final String clusterName, final String brokerAddr, final String brokerName, final long brokerId, final String haServerAddr, final TopicConfigSerializeWrapper topicConfigWrapper, final List<String> filterServerList, final boolean oneway, final int timeoutMills, final boolean enableActingMaster, final boolean compressed, final Long heartbeatTimeoutMillis, final BrokerIdentity brokerIdentity) { final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>(); List<String> nameServerAddressList = this.remotingClient.getAvailableNameSrvList(); if (nameServerAddressList != null && nameServerAddressList.size() > 0) { final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader(); requestHeader.setBrokerAddr(brokerAddr); requestHeader.setBrokerId(brokerId); requestHeader.setBrokerName(brokerName); requestHeader.setClusterName(clusterName); requestHeader.setHaServerAddr(haServerAddr); requestHeader.setEnableActingMaster(enableActingMaster); requestHeader.setCompressed(false); if (heartbeatTimeoutMillis != null) { requestHeader.setHeartbeatTimeoutMillis(heartbeatTimeoutMillis); } RegisterBrokerBody requestBody = new RegisterBrokerBody(); requestBody.setTopicConfigSerializeWrapper(TopicConfigAndMappingSerializeWrapper.from(topicConfigWrapper)); requestBody.setFilterServerList(filterServerList); final byte[] body = requestBody.encode(compressed); final int bodyCrc32 = UtilAll.crc32(body); requestHeader.setBodyCrc32(bodyCrc32); // 创建一个计数器锁(数量为 nameServer 的个数) final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size()); // 循环遍历 for (final String namesrvAddr : nameServerAddressList) { // 往线程池中扔任务 brokerOuterExecutor.execute(new AbstractBrokerRunnable(brokerIdentity) { @Override public void run0() { try { RegisterBrokerResult result = registerBroker(namesrvAddr, oneway, timeoutMills, requestHeader, body); if (result != null) { registerBrokerResultList.add(result); } LOGGER.info("Registering current broker to name server completed. TargetHost={}", namesrvAddr); } catch (Exception e) { LOGGER.error("Failed to register current broker to name server. TargetHost={}", namesrvAddr, e); } finally { // 计数器减1 countDownLatch.countDown(); } } }); } try { // 超时等待都注册完毕 if (!countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS)) { LOGGER.warn("Registration to one or more name servers does NOT complete within deadline. Timeout threshold: {}ms", timeoutMills); } } catch (InterruptedException ignore) { } } return registerBrokerResultList; }
可以看到是通过遍历,向线程池中执行任务,每个线程注册完会让锁减1,最后下边 await 等待所有的线程都注册完毕。
2.2 RocketMQ 中 同步消息发送
RocketMQ 5.0版本消息的同步、异步、一次性发送都是基于 Netty 的异步回调机制来做的,那么对于同步发送来说,需要控制当前线程的阻塞以及超时控制,RocketMQ 这里用到了 CountDownLauch 以及它的 await(发送超时时间)来做的,我们看看:
// NettyRemotingAbstract#invokeSyncImpl 同步消息发送的最后落点 public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException { //get the request id final int opaque = request.getOpaque(); try { // 创建 ResponseFuture // 这里会有把锁 private final CountDownLatch countDownLatch = new CountDownLatch(1); // 锁的计数器为1 final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null); this.responseTable.put(opaque, responseFuture); final SocketAddress addr = channel.remoteAddress(); // 基于 netty 的异步回调机制 operationComplete是回调监听 channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> { if (f.isSuccess()) { responseFuture.setSendRequestOK(true); return; } responseFuture.setSendRequestOK(false); responseTable.remove(opaque); responseFuture.setCause(f.cause()); // responseFuture.putResponse(null); log.warn("Failed to write a request command to {}, caused by underlying I/O operation failure", addr); }); // 阻塞当前线程 同步等待执行结果 即countDownLatch.await 来实现 /** * ResponseFuture#waitResponse * public RemotingCommand waitResponse(final long timeoutMillis) throws InterruptedException { * // 等待 timeoutMillis 即是发送超时时间 默认3秒 * this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); * return this.responseCommand; * } */ RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis); if (null == responseCommand) { if (responseFuture.isSendRequestOK()) { throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis, responseFuture.getCause()); } else { throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause()); } } return responseCommand; } finally { this.responseTable.remove(opaque); } }
3 Semaphore
3.1 RocketMQ 生产者异步发送消息的并发量控制
RocketMQ 中发送消息的方式有一种是异步发送,异步就是将任务提交到异步发送的线程池里,RocketMQ 为了保证服务的稳定,在这里引入了两个信号量锁,一个是控制并发发送数量 一个是控制并发发送的总消息体大小,如下是创建生产者的时候实例化信息:
public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) { // 互相引用 this.defaultMQProducer = defaultMQProducer; this.rpcHook = rpcHook; // 异步消息队列 队列大小默认 5万 this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<>(50000); // 创建异步发送消息线程池 this.defaultAsyncSenderExecutor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(), 1000 * 60, TimeUnit.MILLISECONDS, this.asyncSenderThreadPoolQueue, new ThreadFactoryImpl("AsyncSenderExecutor_")); // 信号量锁 控制最多允许同时发送多少个消息 默认1万 if (defaultMQProducer.getBackPressureForAsyncSendNum() > 10) { semaphoreAsyncSendNum = new Semaphore(Math.max(defaultMQProducer.getBackPressureForAsyncSendNum(), 10), true); } else { semaphoreAsyncSendNum = new Semaphore(10, true); log.info("semaphoreAsyncSendNum can not be smaller than 10."); } // 信号量锁 控制最多同时发送消息的大小 默认100M if (defaultMQProducer.getBackPressureForAsyncSendSize() > 1024 * 1024) { semaphoreAsyncSendSize = new Semaphore(Math.max(defaultMQProducer.getBackPressureForAsyncSendSize(), 1024 * 1024), true); } else { semaphoreAsyncSendSize = new Semaphore(1024 * 1024, true); log.info("semaphoreAsyncSendSize can not be smaller than 1M."); } ... }
然后在异步发送的时候,进行信号量锁的申请:
public void executeAsyncMessageSend(Runnable runnable, final Message msg, final BackpressureSendCallBack sendCallback, final long timeout, final long beginStartTime) throws MQClientException, InterruptedException { ExecutorService executor = this.getAsyncSenderExecutor(); boolean isEnableBackpressureForAsyncMode = this.getDefaultMQProducer().isEnableBackpressureForAsyncMode(); boolean isSemaphoreAsyncNumAquired = false; boolean isSemaphoreAsyncSizeAquired = false; int msgLen = msg.getBody() == null ? 1 : msg.getBody().length; try { // 在开启了降低后台服务压力的时候 默认是不开启的 if (isEnableBackpressureForAsyncMode) { long costTime = System.currentTimeMillis() - beginStartTime; isSemaphoreAsyncNumAquired = timeout - costTime > 0 // 申请信号量锁(并发数量控制) && semaphoreAsyncSendNum.tryAcquire(timeout - costTime, TimeUnit.MILLISECONDS); if (!isSemaphoreAsyncNumAquired) { sendCallback.onException( new RemotingTooMuchRequestException("send message tryAcquire semaphoreAsyncNum timeout")); return; } costTime = System.currentTimeMillis() - beginStartTime; isSemaphoreAsyncSizeAquired = timeout - costTime > 0 // 申请信号量锁(并发消息体大小控制) && semaphoreAsyncSendSize.tryAcquire(msgLen, timeout - costTime, TimeUnit.MILLISECONDS); if (!isSemaphoreAsyncSizeAquired) { sendCallback.onException( new RemotingTooMuchRequestException("send message tryAcquire semaphoreAsyncSize timeout")); return; } } sendCallback.isSemaphoreAsyncSizeAquired = isSemaphoreAsyncSizeAquired; sendCallback.isSemaphoreAsyncNumAquired = isSemaphoreAsyncNumAquired; sendCallback.msgLen = msgLen; executor.submit(runnable); } catch (RejectedExecutionException e) { if (isEnableBackpressureForAsyncMode) { runnable.run(); } else { throw new MQClientException("executor rejected ", e); } } }
4 ReentrantLock
4.1 RocketMQ 中生产者启动后向所有的 Broker 发送心跳
在 DefaultMQProducerImpl 生产者启动的最后:
public void start(final boolean startFactory) throws MQClientException { ...// 向所有Broker发送心跳包,以维持连接 this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); // 启动定时任务,如心跳检测等 RequestFutureHolder.getInstance().startScheduledTask(this); }
sendHeartbeatToAllBrokerWithLock 方法中:
private final Lock lockHeartbeat = new ReentrantLock(); public void sendHeartbeatToAllBrokerWithLock() { // 加锁 if (this.lockHeartbeat.tryLock()) { try { if (clientConfig.isUseHeartbeatV2()) { this.sendHeartbeatToAllBrokerV2(false); } else { this.sendHeartbeatToAllBroker(); } } catch (final Exception e) { log.error("sendHeartbeatToAllBroker exception", e); } finally { this.lockHeartbeat.unlock(); } } else { log.warn("lock heartBeat, but failed. [{}]", this.clientId); } }
5 ReentrantReadWriteLock
6 小结
归纳总结是对技术的巩固以及认识的增强,看看人家别人怎么用的,什么场景下用的,用法上跟自己的有什么不同,甚至在自己写业务的时候,是不是能直接借鉴下,哈哈哈,受益颇多都,所以大家也要学会总结积累哈。本节就主要记录下锁的一些源码应用(会持续增加),有理解不对的地方欢迎指正哈。