Open ZJRui opened 2 years ago
在getIdFromSegmentBuffer 方法中, 假设 当前Segment已经没有可用id了,当前线程A 启动线程B 准备NextSegment。 假设此时已经有100个线程阻塞在取号的方,具体也就是waitAndSleep 方法的地方。 当线程B准备就绪NextSegment后, 线程C及时发现SegmentBuffer准备就绪并先获取到了cpu,这个时候线程C会执行Segment的切换工作。切换之后将SegmentBuffer的nextReady设置为false。 对于线程A和其他被阻塞的线程而言,当这些线程 从waitAndSleep方法返回 后 需要 依次获取写锁,然后从当前Segment中取号。显然这些线程没必要依次获取写锁,而且获取SegmentBuffer写锁 会影响 另外的其他尚未被阻塞线程并发使用读锁从当前Segment中获取id。这是第一种情况。
第二种情况就是,假设线程B 准备nextSegment失败了,此时已经有了100个线程处于阻塞状态waitAndSleep,那么这100个线程会依次获取写锁并检查发现SegmentBuffer的nextReady为false,然后返回 取号失败的结果 这些线程就退出了,因此触发nextSegment的准备工作需要 等待下一次取号请求。
按照我的理解:对于唤醒后的线程,如果SegmentBuffer的nextReady为false 则当前线程 没必要通过获取读锁来取号。 只需要走正常的取号流程就可以了。这个时候对于第二种情况也会有改善,因为走了正常的取号流程 也就相当于会启动新的线程执行nextSegment的初始化工作,而不是直接返回取号失败结果。
public Result getIdFromSegmentBuffer(final SegmentBuffer buffer) { while (true) { buffer.rLock().lock(); try { final Segment segment = buffer.getCurrent(); if (!buffer.isNextReady() && (segment.getIdle() < 0.9 * segment.getStep()) && buffer.getThreadRunning().compareAndSet(false, true)) { service.execute(new Runnable() { @Override public void run() { Segment next = buffer.getSegments()[buffer.nextPos()]; boolean updateOk = false; try { updateSegmentFromDb(buffer.getKey(), next); updateOk = true; logger.info("update segment {} from db {}", buffer.getKey(), next); } catch (Exception e) { logger.warn(buffer.getKey() + " updateSegmentFromDb exception", e); } finally { if (updateOk) { buffer.wLock().lock(); buffer.setNextReady(true); buffer.getThreadRunning().set(false); buffer.wLock().unlock(); } else { buffer.getThreadRunning().set(false); } } } }); } long value = segment.getValue().getAndIncrement(); if (value < segment.getMax()) { return new Result(value, Status.SUCCESS); } } finally { buffer.rLock().unlock(); } waitAndSleep(buffer); //这个地方优化 buffer.wLock().lock(); try { final Segment segment = buffer.getCurrent(); long value = segment.getValue().getAndIncrement(); if (value < segment.getMax()) { return new Result(value, Status.SUCCESS); } if (buffer.isNextReady()) { buffer.switchPos(); buffer.setNextReady(false); } else { //这个地方优化 logger.error("Both two segments in {} are not ready!", buffer); return new Result(EXCEPTION_ID_TWO_SEGMENTS_ARE_NULL, Status.EXCEPTION); } } finally { buffer.wLock().unlock(); } } }
@thelight1 @Yaccc
(只需要走正常的取号流程就可以了。这个时候对于第二种情况也会有改善)您指的是在调用一次getIdFromSegmentBuffer方法吗
在getIdFromSegmentBuffer 方法中, 假设 当前Segment已经没有可用id了,当前线程A 启动线程B 准备NextSegment。 假设此时已经有100个线程阻塞在取号的方,具体也就是waitAndSleep 方法的地方。 当线程B准备就绪NextSegment后, 线程C及时发现SegmentBuffer准备就绪并先获取到了cpu,这个时候线程C会执行Segment的切换工作。切换之后将SegmentBuffer的nextReady设置为false。 对于线程A和其他被阻塞的线程而言,当这些线程 从waitAndSleep方法返回 后 需要 依次获取写锁,然后从当前Segment中取号。显然这些线程没必要依次获取写锁,而且获取SegmentBuffer写锁 会影响 另外的其他尚未被阻塞线程并发使用读锁从当前Segment中获取id。这是第一种情况。
第二种情况就是,假设线程B 准备nextSegment失败了,此时已经有了100个线程处于阻塞状态waitAndSleep,那么这100个线程会依次获取写锁并检查发现SegmentBuffer的nextReady为false,然后返回 取号失败的结果 这些线程就退出了,因此触发nextSegment的准备工作需要 等待下一次取号请求。
按照我的理解:对于唤醒后的线程,如果SegmentBuffer的nextReady为false 则当前线程 没必要通过获取读锁来取号。 只需要走正常的取号流程就可以了。这个时候对于第二种情况也会有改善,因为走了正常的取号流程 也就相当于会启动新的线程执行nextSegment的初始化工作,而不是直接返回取号失败结果。
@thelight1 @Yaccc
https://github.com/Meituan-Dianping/Leaf/pull/191