更改存储在不同线程列表中的对象的字段

Changing field of an object that stored on a list that is on different thread

所以我有两个线程,线程 A 和 B。线程 A 包含一个包含我的对象的 ArrayList。我想要做的是:在线程 B 上,创建新对象并将其添加到该列表中。然后在线程 A 上更改该对象的字段。之后,在线程 B 上读取更改的字段。但遗憾的是,当我在线程 B 上读取它时,我得到了在对象构造函数上创建的默认值。那么,我如何读取新的值?

更新

一些信息:API 在应用程序启动时执行的 onEnable 方法我使用名为“Bukkit”,绑定到 Bukkit 的所有内容都在线程 A 上执行,线程 A 是 Bukkit 的主线程

Main.class:

private static ExecutorService executorService;
private static List<Confirmation> confirmations;

public void onEnable() {
    executorService = Executors.newCachedThreadPool();
    confirmations = new ArrayList<>();
}

public static ExecutorService getExecutorService() {
    return executorService;
}

public static Confirmation createConfirmation(CommandSender sender, String command, String[] args) {
    removeConfirmation(sender, command);
    Confirmation confirmation = new Confirmation(sender, command, args);
    confirmations.add(confirmation);
    return confirmation;
}

public static void removeConfirmation(CommandSender commandSender, String command) {
    confirmations.removeIf(confirmation -> confirmation.getSender().getName().equals(commandSender.getName()) && confirmation.getCommand().equalsIgnoreCase(command));
}

public static Confirmation getConfirmation(CommandSender commandSender, String command) {
    return confirmations.stream().filter(confirmation -> confirmation.getSender().getName().equals(commandSender.getName()) && confirmation.getCommand().equalsIgnoreCase(command)).findFirst().orElse(null);
}

public static Confirmation createConfirmationIfNull(CommandSender commandSender, String command, String[] args) {
    Confirmation confirmation = getConfirmation(commandSender, command);
    return confirmation != null ? confirmation : createConfirmation(commandSender, command, args);
}

Confirmation.class:

private final CommandSender sender;
private final String command;
private final String[] args;
private boolean confirmed;

public Confirmation(CommandSender sender, String command, String[] args) {
    this.sender = sender;
    this.command = command;
    this.args = args;
    this.confirmed = false;
}

public CommandSender getSender() {
    return sender;
}

public String getCommand() {
    return command;
}

public String[] getArgs() {
    return args;
}

public boolean isConfirmed() {
    return confirmed;
}

public void setConfirmed(boolean bln) {
    this.confirmed = bln;
}

Command.class:

@CommandHandler(name = "rank", usage = "/rank <set|prefix>", requiredRank = Rank.ADMINISTRATOR)
public void rank(CommandSender sender, Command command, String s, String[] args) { //Always called on Thread A
    String str = args[0];
    RankCommandAction action = Arrays.stream(RankCommandAction.values()).filter(rankCommandAction -> rankCommandAction.getName().equalsIgnoreCase(str)).findFirst().orElse(null);
    if (action == null) {
        sender.sendMessage(ChatColor.RED + "Usage: /rank <set|prefix>");
        return;
    }

    Main.getExecutorService().submit(() -> action.getConsumer().accept(sender, args)); //Thread B
}

private enum RankCommandAction {

    SET_PREFIX("prefix", (CommandSender sender, String[] args) -> {
        CommandConfirmationEvent event = new CommandConfirmationEvent(sender, "rank", args, ChatColor.RED + "Are you sure?");
        Bukkit.getPluginManager().callEvent(event);

        if (!event.isCancelled()) {
            sender.sendMessage("Prefix changed");
        }
    }),


    SET_RANK("set", (CommandSender sender, String[] args) -> {
        CommandConfirmationEvent event = new CommandConfirmationEvent(sender, "rank", args, ChatColor.RED + "Are you sure?");
        Bukkit.getPluginManager().callEvent(event);

        if (!event.isCancelled()) {
            sender.sendMessage("Changed rank");
        }
    });

CommandConfirmationEvent.class:

private final CommandSender commandSender;
private final String command;
private final String[] args;

private final Confirmation confirmation;
private String warningMessage;

public CommandConfirmationEvent(CommandSender commandSender, String command, String[] args, String warningMessage) {
    this.commandSender = commandSender;
    this.command = command;
    this.args = args;
    this.warningMessage = warningMessage;
    this.confirmation = Main.createConfirmationIfNull(commandSender, command, args);
}

public CommandSender getCommandSender() {
    return commandSender;
}

public String getCommand() {
    return command;
}

public String[] getArgs() {
    return args;
}

public Confirmation getConfirmation() {
    return confirmation;
}

public String getWarningMessage() {
    return warningMessage;
}

public void setWarningMessage(String warningMessage) {
    this.warningMessage = warningMessage;
}

@Override
public boolean isCancelled() {
    return this.confirmation != null && !this.confirmation.isConfirmed();
}

这是调用事件时实际执行的内容:

if (!event.getConfirmation().isConfirmed()) {
    event.getCommandSender().sendMessage("Please confirm");
    return;
}
Main.removeConfirmation(event.getConfirmation());

最后,这是确认命令(在线程 A 上):

@CommandHandler(name = "confirm")
public void confirm(CommandSender sender, Command cmd, String str, String[] args) { //Always called on Thread A
    Confirmation confirmation = findConfirmation(sender, "confirm", args);

    if (confirmation != null) {
        confirmation.setConfirmed(true);
        String[] arg = confirmation.getArgs();
        Bukkit.dispatchCommand(sender, confirmation.getCommand() + (arg != null && arg.length > 0 ? " " + StringUtils.merge(arg, " ") : "")); //This runs the "rank" command again
        Main.removeConfirmation(confirmation);
    }
}

private Confirmation findConfirmation(CommandSender sender, String[] args) {
    List<Confirmation> list = Main.getConfirmations(sender);

    if (list.isEmpty())
        return null;

    Confirmation confirmation;
    if (list.size() > 1) {
        if (args.length == 0) {
            sender.sendMessage(ChatColor.RED + "Since you have multiple confirmations awaiting, you must specify the confirmation you want to confirm.");
            sender.sendMessage(ChatColor.RED + "Usage: /confirm <command>");
            sender.sendMessage(ChatColor.RED + "Awaiting confirmations: " + StringUtils.merge(list.stream().map(Confirmation::getCommand).toArray(String[]::new), ", ") + ".");
            return null;
        } else {
            Confirmation foundConfirmation = list.stream().filter(confirmation1 -> confirmation1.getCommand().equalsIgnoreCase(args[0])).findFirst().orElse(null);
            if (foundConfirmation == null) {
                sender.sendMessage(ChatColor.RED + "You don't have any confirmations for the command '" + args[0] + "'.");
                return null;
            } else {
                confirmation = foundConfirmation;
            }
        }
    } else {
        confirmation = list.get(0);
    }

    return confirmation;
}

您应该在两个线程上使用相同的对象实例,并在访问该对象时同步它们。

你的描述“Thread A contains an Arraylist”没有任何意义。如果它是局部变量,线程 B 永远无法访问它。

问题

  1. 根据给定的代码,它可能会在这一行抛出 IndexOutOfBounds 异常 objects.get(0).setName("TEST");

原因

  1. 提交给执行者服务的任务将运行在自己的时间轴上(除了休眠1000
  2. 主线程休眠 200 无济于事(即使增加到 2000 也可能无济于事)

应该做什么

  1. 创建两个倒计时闩锁
  2. 将一个 latch 传递给提交的任务
  3. 设置后,会在latch上倒计时,等待其他latch
  4. main 将等待任务
  5. 倒计时的锁存器
  6. 主线程,一旦收到任务线程的通知,就会在其他latch上进行更新和倒计时
  7. 现在任务将收到通知并打印更新后的值
    static class MyObject {
        volatile String value;
        MyObject(String value) {
            this.value = value;
        }
    }
    public static void main(String[] args) {
        final List<MyObject> objects = new ArrayList<>();
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        CountDownLatch updateLatch = new CountDownLatch(1);
        CountDownLatch addLatch = new CountDownLatch(1);
        executorService.submit(() -> {
            objects.add(new MyObject("NAME"));
            try {
                addLatch.countDown();
                updateLatch.await();
                System.out.println(objects.get(0).value); //This prints "NAME"
            } catch (Exception e) {
                e.printStackTrace();
            }
        });


        try {
            addLatch.await();
            objects.get(0).value = "TEST";
            updateLatch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

我发现了问题:我在执行确认命令时从列表中删除了对象,它在 运行 再次执行“排名”命令之前删除了对象。这导致第二次“排名”命令执行找不到确认对象。