文章目录
  1. 1. Agents
    1. 1.1. Yielding Idle Strategy (空闲让步策略)
    2. 1.2. Idle Strategies(空闲策略)
      1. 1.2.1. 实现自定义空闲策略
      2. 1.2.2. Scheduling Agents(代理调度机制)
    3. 1.3. 小结
      1. 1.3.1. 复习:java线程生命周期
    4. 1.4. 下一篇
    5. 1.5. 附录

在2006 年,爱德华·李 (Edward A. Lee) 撰写了一篇题为“线程中的问题”的技术报告。 在技术报告中,他有以下说法:

线程,是非常不确定的一种计算模型。而程序员的工作就是要修剪这种不确定性。

尽管许多技术研究通过提供更加有效的裁减措施来改进线程模型,但我(爱德华本人)认为这是在开倒车。

我们应该从本质上构建确定性的、可组合的组件,而不是去裁减这种非确定性。

应该在需要的时候明确并且明智地引入非确定性,而不是在不需要它的地方将其删除。

对于Aeron而言,Agrona Agents(Agrona代理) 和 Idle Strategies(空闲策略) 是实现爱德华的建议的一种实现方式。

当与 Aeron 一起使用时,Agrona Agents允许基于开发人员以易于推理的方式,安全地构建确定性的资源,从而管理线程。

Agents

Agrona Agents是在应用程序逻辑中执行的,具备工作周期的容器。

例如处理 Aeron 中订阅的消息。

Agents的工作周期间隔和CPU消耗地增加,由空闲策略进行控制。 Agents可以调度在专用的线程上,当然也可以作为一组复合Agents(a composite group of agents)的一部分在单个线程上运行。

典型的Agents工作周期是通过轮询Agents的 doWork 函数实现的,一旦doWork方法返回零,就会回调选中的空闲策略,代码逻辑如下。

1
2
3
4
5
6
7
8
9
10
11
12
public final class Sample implements Agent
{
...
public int doWork()
{
int workCount = 0;
workCount += dutyCyclePart1();
workCount += dutyCyclePart2();
return workCount;
}
...
}

Yielding Idle Strategy (空闲让步策略)

下面这段代码是 Yielding Idle Strategy (中译为:空闲让步策略)的关键部分,它展示了对workCount值进行校验的关键逻辑模板。如果 workCount 结果小于等于零,则Agents线程将由“运行态Running”转为就绪态“Runnable”,让渡cpu时间片供自己及其余同优先级线程进行时间片抢占。

1
2
3
4
5
6
7
8
9
10
public final class YieldingIdleStrategy implements IdleStrategy
{
...
public void idle(int workCount) {
if (workCount <= 0) {
Thread.yield();
}
}
...
}

接着这些 Agent 和 IdleStrategy 会在 AgentRunner 中组合在一起,代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class AgentRunner implements Runnable, AutoCloseable
{
...
public void run()
{
...
// 当线程运行态
while (isRunning)
{
// 执行工作周期的逻辑
doDutyCycle(idleStrategy, agent);
}
...
}
private void doDutyCycle(final IdleStrategy idleStrategy, final Agent agent)
{
...
// 执行agent的doWork方法,即业务逻辑
final int workCount = agent.doWork();
// 根据返回值决定是否要进行执行空闲策略
idleStrategy.idle(workCount);
...
}
...
}

Idle Strategies(空闲策略)

Agrona 提供了一系列默认的空闲策略,但是如果以后需要,开发者很容易实现自定义的空闲策略。

默认空闲策略及其解释如下:

  • SleepingIdleStrategy(休眠空闲策略): 通过使用 parkNanos 将线程阻塞在给定的时间段内实现休眠;

    1
    2
    3
    4
    5
    public void idle(int workCount) {
    if (workCount <= 0) {
    LockSupport.parkNanos(this.sleepPeriodNs);
    }
    }
  • SleepingMillisIdleStrategy(另一种休眠空闲策略):这是基于thread.sleep(millseconds)方法实现的在某个给定时间段内使线程阻塞的空闲策略。 适用于在低规格机器上进行本地开发或者在运行了大量进程的机器上使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void idle(int workCount) {
    if (workCount <= 0) {
    try {
    Thread.sleep(this.sleepPeriodMs);
    } catch (InterruptedException var3) {
    Thread.currentThread().interrupt();
    }
    }
    }
  • YieldingIdleStrategy(让步空闲策略):使用 thread.yield 来让出线程控制权的策略,在上文我们已经专门解释过;

    1
    2
    3
    4
    5
    public void idle(int workCount) {
    if (workCount <= 0) {
    Thread.yield();
    }
    }
  • BackoffIdleStrategy(退避空闲策略): 这是一种激进的策略,它的机制为先是自旋,然后过渡到让步,然后再过渡到阻塞在可进行配置的纳秒时间内的一种复合策略。 这是 Aeron Cluster 的默认策略。可以看到它的idle()方法是随着运行不断升级空闲处理策略的(是不是类似synchronized锁膨胀)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public void idle() {
    switch (this.state) {
    case 0:
    this.state = 1;
    // 进入自旋逻辑
    ++this.spins;
    break;
    case 1:
    // 自旋
    ThreadHints.onSpinWait();
    if (++this.spins > this.maxSpins) {
    this.state = 2;
    this.yields = 0L;
    }
    break;
    case 2:
    // 让步cpu
    if (++this.yields > this.maxYields) {
    this.state = 3;
    this.parkPeriodNs = this.minParkPeriodNs;
    } else {
    Thread.yield();
    }
    break;
    case 3:
    // 阻塞
    LockSupport.parkNanos(this.parkPeriodNs);
    this.parkPeriodNs = Math.min(this.parkPeriodNs << 1, this.maxParkPeriodNs);
    }
    }
  • NoOpIdleStrategy:这是目前可用的最激进的空闲策略。顾名思义,什么操作都不做,也就是根本不会空闲。

1
2
3
4
5
public void idle(int workCount) {
}
public void idle() {
}
  • BusySpinIdleStrategy: 这将调用 java.lang.Thread.onSpinWait()方法。 这个策略在Java 9+版本的JVM运行时可用。 它向 CPU 提供了一个微弱的标识:如果线程处于一个循环中并且正忙于等待某个操作,那么CPU可能会在不执行OS系统级调度的前提下将额外的资源分配给另一个线程。(也就是CPU会对这种处于循环忙等的线程进行资源调度,分配资源给其他线程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void idle(int workCount) {
if (workCount <= 0) {
ThreadHints.onSpinWait();
}
}
// org.agrona.hints.ThreadHints#onSpinWait
public static void onSpinWait() {
if (null != ON_SPIN_WAIT_METHOD_HANDLE) {
try {
ON_SPIN_WAIT_METHOD_HANDLE.invokeExact();
} catch (Throwable var1) {
}
}
}

实现自定义空闲策略

如果你想要实现自定义空闲策略,则只需要实现 IdleStrategy 接口,即:

1
2
3
4
5
public interface IdleStrategy {
void idle(int count);
void idle();
void reset();
}

Scheduling Agents(代理调度机制)

如果要开启Agent的生命周期,那么你需要决定如何对它进行调度。

  • 你可以让Agent在 Agrona 提供的一个线程上运行;
  • 你也可以提供一个线程工厂(thread factory)来运行你的Agent;
  • 您可以从一组Agent(a collection of agents)中构造一个 CompositeAgent,然后你就可以将这些Agent作为一个调度单元进行调度。

注意: 并不是所有的空闲策略都是线程安全的。因此通常我们建议为每个被调度的Agent制定不同的空闲策略。(专策略专用)

一旦你已经决定了如何执行一个Agent,那么就可以使用 Agent Runner 启动它。

下面是代码示例:

1
2
3
final AgentRunner runner =
new AgentRunner(idleStrategy, errorHandler, errorCounter, agent);
AgentRunner.startOnThread(runner);

构造一个AgentRunner实例,构造参数中,

  • idleStrategy可以设置为上述任何空闲策略之一,
  • 最后一个参数为一个Agent引用实例。
  • errorHandler(错误处理器)通常会接受字符串,并捕获引发的错误。
  • errorCounter(错误计数器)用于计算该Agent引发的错误。

最后通过调用AgentRunner.startOnThread 告诉 Agrona 将Agent调度在一个新线程上。

小结

到此我们就基本了解了Aeron中的Agrona Agent及其空闲策略的分类及基本使用,如果需要进一步学习,请自行参考完整代码示例:https://aeroncookbook.com/aeron-cookbook/ipc/

复习:java线程生命周期

最后我们一起简单复习一下Java中线程的生命周期,温故知新。

  • 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new Thread();

  • 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

  • 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

  • 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

    • 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

    • 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

    • 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

下一篇

频道,流,以及会话 (Channels, Streams and Sessions)

附录

本文原文链接:https://aeroncookbook.com/agrona/agents-idle-strategies/



版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

文章目录
  1. 1. Agents
    1. 1.1. Yielding Idle Strategy (空闲让步策略)
    2. 1.2. Idle Strategies(空闲策略)
      1. 1.2.1. 实现自定义空闲策略
      2. 1.2.2. Scheduling Agents(代理调度机制)
    3. 1.3. 小结
      1. 1.3.1. 复习:java线程生命周期
    4. 1.4. 下一篇
    5. 1.5. 附录
Fork me on GitHub