如何使用 Timers 执行带有动态定时器的技能效果?

How to execute skill effects with dynamic timers using Timers?

目前,我正在使用定时器来执行我的技能效果。所有技能效果每3秒心跳

这是我的代码:

public class EffectServiceImpl extends AbsServiceAdaptor<EffectConfig> {
    private static EffectServiceImpl instance;
    private FastList<Monster> existsEffectMonsterList;
    private Timer timer;
    public static EffectServiceImpl getInstance() {
        if(null == instance) {
            instance = new EffectServiceImpl();
        }
        return instance;
    }
    private EffectServiceImpl() {
        existsEffectMonsterList = new FastList<Monster>();
        timer = new Timer();
        timer.schedule(new MonsterEffectCheckTask(), 10000, 3000); // Heartbeat every 3 seconds
    }
    public class MonsterEffectCheckTask extends TimerTask {
        @Override
        public void run() {
            if(existsEffectMonsterList.size() > 0) {
                Monster monster;
                Effect effect;
                for(int i = 0; i < existsEffectMonsterList.size();) {
                    monster = existsEffectMonsterList.get(i);
                    if(monster.effectList.size() > 0) {
                        for(int j = 0; j < monster.effectList.size();) {
                            try {
                                effect = monster.effectList.get(j);
                                if(effect.heartbeat(monster)) {
                                    j++;
                                }
                            }
                            catch(Exception e) {
                                e.printStackTrace();
                                break;
                            }
                        }
                    }
                    if(monster.effectList.size() == 0) {
                        existsEffectMonsterList.remove(i);
                    }
                    else {
                        i++;
                    }
                }
            }
        }
    }
}

不过我希望不是所有的技能效果都做心跳3秒。会有小于3秒或大于3秒(即动态周期)的心跳技能。

所以我将 timer.schedule 处的周期更改为 1:

    ...
        timer.schedule(new MonsterEffectCheckTask(), 10000, 1);
    ...

然后将Thread.sleep添加到TimerTask:

...
if(monster.effectList.size() > 0) {
    for(int j = 0; j < monster.effectList.size();) {
        try {
            effect = monster.effectList.get(j);
            if(effect.heartbeat(monster)) {
                j++;
            }
            Thread.sleep(effect.execTime); // This is dynamic time, each effect has an `execTime`. Code `public int execTime;`
        }
        catch(InterruptedException e) {
                e.printStackTrace();
        }
        catch(Exception e) {
            e.printStackTrace();
            break;
        }
    }
}
...

如果是上面的代码,我应该:使用 schedulescheduleAtFixedRate 或任何其他?如果想“暂停”计时器,是否有任何解决方案可以替换 Thread.sleep ?我应该如何设置 delayperiod(我知道如果我将它设置为 0 我会得到一个 IllegalArgumentException,但如果设置为 1 就太短了)?我希望效果在“暂停”时间到期后立即成为心跳。此外,相同的心跳可以有很多效果,但每个效果的“暂停”时间是不一样的。

非常感谢您的回答。

执行器框架

Timer 现在通常被 Executors 框架淘汰。参见 tutorial by Oracle

将您的任务定义为 RunnableCallable 个对象。

使用由适当数量的线程支持的计划执行程序服务。然后按您选择的时间间隔将每个任务重复安排到 运行。

要检查或取消每个任务,请捕获返回给您的 ScheduledFuture 对象。

如果您想改变任务执行之间的时间间隔量,请通过其 ScheduledFuture 对象取消任务。然后使用执行程序服务重新安排新的时间间隔。

始终在您的应用程序结束之前关闭执行程序服务。否则后台线程池可能会像僵尸一样无限期地继续下去。

并包装您的任务代码以捕获任何无意中冒出的异常。否则以后的工作会停滞不前。

所有这些都已在 Stack Overflow 上多次提及。所以我很简短。搜索以了解更多信息。下次发帖前彻底搜索 Stack Overflow。

示例代码

我制作了一个示例应用程序来展示使用执行器框架更简单但更强大地管理后台线程工作的基础知识。

我们定义了一个 Monster 接口,其中有两个实现 classes,DemonWitch。我创建了这些 classes 的一些对象。 classes 带有一个 actOut 方法。我们的后台任务将是运行这个方法。

对于每个现有的 Monster 实例,我们定义一个 Runnable,它只是调用该实例的 Monster::actOut 方法。我们通过 ScheduledExecutorService 对象每隔几秒安排每个 运行nable 到 运行。该执行程序服务 returns 一个 ScheduledFuture 对象来跟踪我们安排的每个 运行 启用。我们将这些未来对象收集到 Demon 列表和 Witch.

列表中

我们让这个 运行 一分钟。在那段时间过去后,我们更改 Witch 任务的节奏。我们取消他们当前的任务,然后安排一个新的 运行nable 对象,执行间隔更长。我们让 运行 再等一分钟。在那之后,我们关闭应用程序。

(顺便说一句,我们可以回收现有的 Runnable 对象,而不是在重新安排时实例化新的对象。根据您的需要做任何有意义的事情。)

此代码使用了 Java 15 中预览的一些即将推出的功能,例如 sealed types and records。但是这些对这里的概念并不重要;您可以使用以前版本的 Java.

轻松地重新执行此代码

样本运行

示例运行:

---------|  Starting  |--------------------------------------------
This monster Demon named Crowley buys a soul at 2020-12-08T02:32:39Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:32:39Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:32:41Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:32:41Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:32:46Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:32:46Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:32:51Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:32:51Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:32:56Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:32:56Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:32:59Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:32:59Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:01Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:01Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:06Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:06Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:11Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:11Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:16Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:16Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:33:19Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:33:19Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:21Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:21Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:26Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:26Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:31Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:31Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:36Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:36Z
---------|  Changing speeds  |--------------------------------------------
This monster Witch named Rowena casts a spell at 2020-12-08T02:33:38Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:33:38Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:33:39Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:33:39Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:33:59Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:33:59Z
This monster Witch named Rowena casts a spell at 2020-12-08T02:34:18Z
This monster Witch named Tasha casts a spell at 2020-12-08T02:34:18Z
This monster Demon named Crowley buys a soul at 2020-12-08T02:34:19Z
This monster Demon named Barthamus buys a soul at 2020-12-08T02:34:19Z
---------|  Ending  |--------------------------------------------

源代码

我们有四个 classes:

  • Monster.java(我们业务对象的接口)
    • Demon.java(具体class)
    • Witch.java(具体class)
  • MonsterMash.java(应用class)

代码:

package work.basil.example;

public sealed interface Monster
        permits Demon, Witch
{
    String name ( ); // This getter method implemented implicitly by `record` in our concrete classes `Demon` & `Witch`.

    public void actOut ( );
}
package work.basil.example;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

public record Demon(String name) implements Monster
{
    @Override
    public void actOut ( )
    {
        System.out.println( "This monster " + this.getClass().getSimpleName() + " named " + this.name + " buys a soul at " + Instant.now().truncatedTo( ChronoUnit.SECONDS ) );
    }
}
package work.basil.example;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

public record Witch(String name) implements Monster
{
    @Override
    public void actOut ( )
    {
        System.out.println( "This monster " + this.getClass().getSimpleName() + " named " + this.name + " casts a spell at " + Instant.now().truncatedTo( ChronoUnit.SECONDS ) );
    }
}
package work.basil.example;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * Example for showing a change in cadence to scheduled tasks.
 */
public class MonsterMash
{
    public static void main ( String[] args )
    {
        System.out.println( "Hello World!" );
        MonsterMash app = new MonsterMash();
        app.demo();
    }

    private void demo ( )
    {
        // Instantiate Monster objects.
        List < Monster > monsters = List.of(
                new Demon( "Crowley" ) ,
                new Demon( "Barthamus" ) ,
                new Witch( "Rowena" ) ,
                new Witch( "Tasha" )
        );
        System.out.println( "monsters = " + monsters );

        // Schedule each monster to do some work periodically.
        System.out.println( "---------|  Starting  |--------------------------------------------" );
        ScheduledExecutorService ses = null;
        try
        {
            ses = Executors.newSingleThreadScheduledExecutor();

            final List < ScheduledFuture > demonFutures = new ArrayList <>();
            final List < ScheduledFuture > witchFutures = new ArrayList <>();
            for ( Monster monster : monsters )
            {
                Runnable runnable = monster :: actOut;  // Method reference used here for shorter syntax. 

                // Remember these futures.
                if ( monster instanceof Demon )
                {
                    ScheduledFuture scheduledFuture = ses.scheduleAtFixedRate( runnable , 1 , 20 , TimeUnit.SECONDS );
                    demonFutures.add( scheduledFuture );
                }
                if ( monster instanceof Witch )
                {
                    ScheduledFuture scheduledFuture = ses.scheduleAtFixedRate( runnable , 3 , 5 , TimeUnit.SECONDS );
                    witchFutures.add( scheduledFuture );
                }
            }

            // Wait a while to let the scheduled tasks do their work on background thread(s).
            // Then alter the cadence of the tasks’ execution.
            try
            {
                Thread.sleep( TimeUnit.MINUTES.toMillis( 1 ) );
                System.out.println( "---------|  Changing speeds  |--------------------------------------------" );
                for ( ScheduledFuture witchFuture : witchFutures )
                {
                    witchFuture.cancel( false );
                }
                witchFutures.clear();
                for ( Monster monster : monsters )
                {
                    if ( monster instanceof Witch )
                    {
                        Runnable runnable = monster :: actOut;
                        ScheduledFuture scheduledFuture = ses.scheduleAtFixedRate( runnable , 0 , 40 , TimeUnit.SECONDS );
                        witchFutures.add( scheduledFuture );
                    }
                }
                Thread.sleep( TimeUnit.MINUTES.toMillis( 1 ) );

                System.out.println( "---------|  Ending  |--------------------------------------------" );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace();
            }
        }
        finally
        {
            if ( Objects.nonNull( ses ) ) { ses.shutdown(); }
        }
    }
}