多线程与多任务
Bukkit 的调度框架在 org.bukkit.scheduler 包下。掌握它能做延时任务、定时循环、异步执行重活——但线程安全是这里的核心红线。
核心:tick
Bukkit 调度以**游戏刻(tick)**为时间单位。理想情况 1 tick = 50ms,20 tick = 1 秒。受性能影响实际 tick 不一定精确(用 /tps 查每秒 tick 数)。
换算
- 1 秒 ≈ 20 tick
- 5 秒 ≈ 100 tick
- 1 分钟 ≈ 1200 tick
包结构
BukkitScheduler:调度器,负责创建/管理任务(类似线程池)BukkitTask:单个任务的句柄,可取消、查 idBukkitRunnable:抽象类,对BukkitScheduler的封装,用起来更简洁
两个入口
入口一:Bukkit.getScheduler()
java
Bukkit.getScheduler().runTask(this, new Runnable() {
@Override
public void run() {
// 逻辑
}
});入口二:BukkitRunnable(推荐)
java
new BukkitRunnable() {
@Override
public void run() {
// 逻辑
}
}.runTaskLater(this, 20L); // 20 tick 后执行一次BukkitRunnable 更简洁,还能在 run() 内调 cancel() 自我取消。
四类调度方法
| 方法 | 时机 | 线程 |
|---|---|---|
runTask(plugin, runnable) | 下一 tick | 主线程 |
runTaskAsynchronously(plugin, runnable) | 下一 tick | 异步线程 |
runTaskLater(plugin, runnable, delay) | delay tick 后 | 主线程 |
runTaskLaterAsynchronously(...) | delay tick 后 | 异步 |
runTaskTimer(plugin, runnable, delay, period) | delay 后开始,每 period 重复 | 主线程 |
runTaskTimerAsynchronously(...) | 同上 | 异步 |
同步任务(主线程)
java
new BukkitRunnable() {
@Override
public void run() {
Bukkit.broadcastMessage("5 秒到了!");
}
}.runTaskLater(this, 100L); // 100 tick = 5 秒后定时循环
java
new BukkitRunnable() {
int count = 0;
@Override
public void run() {
count++;
Bukkit.broadcastMessage("第 " + count + " 次广播");
if (count >= 5) {
cancel(); // 自我取消
}
}
}.runTaskTimer(this, 0L, 100L); // 立即开始,每 100 tick 一次异步任务
java
new BukkitRunnable() {
@Override
public void run() {
// 耗时操作:查数据库、读大文件、HTTP 请求
// ... 这里不能调大部分 Bukkit API!
}
}.runTaskAsynchronously(this);线程安全红线
异步线程绝不能直接操作 Bukkit API
绝大部分 Bukkit API 不是线程安全的。在异步线程里调 player.sendMessage、改方块、生成实体、Bukkit.getPlayer 等,会导致数据竞争、 ConcurrentModification、甚至崩服。
异步线程只能做"纯计算/IO"(数据库、文件、网络)。做完后要把结果切回主线程再操作游戏:
java
new BukkitRunnable() {
@Override
public void run() {
// 异步:查数据库
int coins = queryDatabase(playerName);
// 切回主线程操作游戏
new BukkitRunnable() {
@Override
public void run() {
Player p = Bukkit.getPlayer(playerName);
if (p != null) p.sendMessage("你有 " + coins + " 金币");
}
}.runTask(plugin);
}
}.runTaskAsynchronously(plugin);注意事项
- 别用过时方法:
scheduleAsyncDelayedTask等已废弃,用上面的runTask* - 同步任务别做重活:主线程卡 = 全服卡(TPS 掉)。耗时操作丢异步
- 定时任务记得取消:
BukkitTask可cancel(),或BukkitRunnable内cancel()。不取消的任务会一直跑、泄漏 - 插件禁用时清理:在
onDisable里取消所有自己调度的任务,或用Bukkit.getScheduler().cancelTasks(this)
何时用同步、何时用异步
| 场景 | 用 |
|---|---|
| 改方块、生成实体、发消息、调 Bukkit API | 同步(主线程) |
| 查数据库、读写大文件、HTTP 请求、重计算 | 异步,结果切回主线程 |
| 延时执行简单逻辑 | 同步 runTaskLater |
| 定时循环(每秒广播) | 同步 runTaskTimer |
调度器是性能与并发的关键。下一篇自定义合成表。
