在 Spigot MC 中,有关讨论帖 https://www.spigotmc.org/threads/accessing-modifying-inventory-slots-async.376967/ 内多位用户质疑了物品栏的线程安全性。他们自述异步任务内修改物品栏没有遇到过并发问题,然而物品栏的修改不是线程安全的,仍有发生冲突之虞。
查看 CraftInventory 的相关子类可见,CraftInventory 不会存储物品堆。物品堆存储在 NMS 的 IInventory 相关实现类中。如 CraftInventoryPlayer 类对应 NMS 中 PlayerInventory 类。CraftInventoryPlayer 的 setItem 方法首先转发调用 PlayerInventory 的同名方法。而 PlayerInventory 中的方法则是操作了类型为 NonNullList 的集合以存储物品堆。NonNullList 类底层是 Arrays$ArrayList,因为其构造器内有:
复制代码在各 setItem 方法没有对并发访问加以控制的情况下,异步修改物品栏与并发修改 Arrays$ArrayList 无异。这里 setItem 方法的循环用于确定要操作哪个 List 。比如 setItem 传入的序号系 4 个盔甲槽之一,就操作盔甲槽的 NonNullList<ItemStack> ,以此类推。物品栏主要部分、盔甲槽位部分和副手槽位部分分别在 3 个列表内。
异步修改不出现报错,也是正常的。Arrays$ArrayList 不是 java.util.ArrayList ,不支持 remove 等方法。这三个列表的长度是确定的。这三个列表在初始化时将每一项全部设为 ItemStack.EMPTY ,即空物品堆。添加物品堆实质上是将空物品堆替换为某个指定物品堆,删除物品堆实质上是将某个指定序号上的物品堆替换为空物品堆。因此列表的操作实质上全为不改变长度前提下的 set,并不会出现任何报错。按:A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification. 所以如果单纯从有无报错的角度而言,不出现报错。
目前我所想到的问题主要在于执行顺序问题,如:
复制代码这样写,输出有时是 true 有时是 false 。存在两个异步延时任务的概率并不是非常高,勉强认为异步操作物品栏在部分情况下是可以接受的。这主要是由于 SpigotMC 上的帖子提出某个物品栏操作耗时 0.25 秒,虽然我暂时不确定是怎么写的。来自群组: PVPINStudio
查看 CraftInventory 的相关子类可见,CraftInventory 不会存储物品堆。物品堆存储在 NMS 的 IInventory 相关实现类中。如 CraftInventoryPlayer 类对应 NMS 中 PlayerInventory 类。CraftInventoryPlayer 的 setItem 方法首先转发调用 PlayerInventory 的同名方法。而 PlayerInventory 中的方法则是操作了类型为 NonNullList 的集合以存储物品堆。NonNullList 类底层是 Arrays$ArrayList,因为其构造器内有:
- private final List<E> a;
- private final E b;
- public static <E> NonNullList<E> a(int var0, E var1) {
- Validate.notNull(var1);
- Object[] var2 = new Object[var0];
- Arrays.fill(var2, var1);
- return new NonNullList<>(Arrays.asList((E[])var2), var1);
- }
- protected NonNullList(List<E> var0, @Nullable E var1) {
- this.a = var0;
- this.b = var1;
- }
- public E set(int var0, E var1) {
- Validate.notNull(var1);
- return this.a.set(var0, var1);
- }
异步修改不出现报错,也是正常的。Arrays$ArrayList 不是 java.util.ArrayList ,不支持 remove 等方法。这三个列表的长度是确定的。这三个列表在初始化时将每一项全部设为 ItemStack.EMPTY ,即空物品堆。添加物品堆实质上是将空物品堆替换为某个指定物品堆,删除物品堆实质上是将某个指定序号上的物品堆替换为空物品堆。因此列表的操作实质上全为不改变长度前提下的 set,并不会出现任何报错。按:A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification. 所以如果单纯从有无报错的角度而言,不出现报错。
目前我所想到的问题主要在于执行顺序问题,如:
- var plugin = Test.getProvidingPlugin(Test.class);
- for (int i = 0; i < 1000; i++) {
- var inv = Bukkit.createInventory(null, 54, "test");
- inv.setItem(0, new ItemStack(Material.DIAMOND));
- Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
- inv.setItem(0, new ItemStack(Material.GOLD_INGOT));
- }, 1L);
- Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
- System.out.println(inv.contains(Material.DIAMOND));
- }, 1L);
- }
两个任务同时设置物品栏某一格的物品可能较难出现,但两个任务同时addItem很容易出现。这种时候如果两个任务都是异步操作,东西就没了