Solved Concurrentmodificationexception when working with cooldowns

Discussion in 'Spigot Plugin Development' started by gordanb740, Mar 23, 2021.

  1. I have made a custom weapon that strikes lightning whenever used by any LivingEntity, as well as a 10 second cooldown on the lightning effect. It works flawlessly when used by players but when a zombie or skeleton uses the weapon, sometimes an error shows up in the console. I know it has something to do with the cooldown since it never happens whenever I remove it.

    Here is the error message I get:
    Code (Text):
    java.util.ConcurrentModificationException: null
        at java.util.HashMap$HashIterator.nextNode(Unknown Source) ~[?:1.8.0_241]
        at java.util.HashMap$KeyIterator.next(Unknown Source) ~[?:1.8.0_241]
        at com.User7.SuperWeapons.Cooldowns$1.run(Cooldowns.java:18) ~[?:?]
        at org.bukkit.craftbukkit.v1_16_R3.scheduler.CraftTask.run(CraftTask.java:81) ~[spigot-1.16.4.jar:git-Spigot-37d799b-3eb7236]
        at org.bukkit.craftbukkit.v1_16_R3.scheduler.CraftScheduler.mainThreadHeartbeat(CraftScheduler.java:400) ~[spigot-1.16.4.jar:git-Spigot-37d799b-3eb7236]
        at net.minecraft.server.v1_16_R3.MinecraftServer.b(MinecraftServer.java:1059) ~[spigot-1.16.4.jar:git-Spigot-37d799b-3eb7236]
        at net.minecraft.server.v1_16_R3.DedicatedServer.b(DedicatedServer.java:355) ~[spigot-1.16.4.jar:git-Spigot-37d799b-3eb7236]
        at net.minecraft.server.v1_16_R3.MinecraftServer.a(MinecraftServer.java:1007) ~[spigot-1.16.4.jar:git-Spigot-37d799b-3eb7236]
        at net.minecraft.server.v1_16_R3.MinecraftServer.w(MinecraftServer.java:846) ~[spigot-1.16.4.jar:git-Spigot-37d799b-3eb7236]
        at net.minecraft.server.v1_16_R3.MinecraftServer.lambda$0(MinecraftServer.java:164) ~[spigot-1.16.4.jar:git-Spigot-37d799b-3eb7236]
        at java.lang.Thread.run(Unknown Source) [?:1.8.0_241]
    Here is the code for checking the cooldown:
    Code (Java):
                    UUID uuid = attacker.getUniqueId();
                    if(!Cooldowns.abilityCooldown.containsKey(uuid)) {
                        attacked.getWorld().strikeLightningEffect(attacked.getLocation());
                        attacked.setHealth(attacked.getHealth() - 3.0D);
                        Cooldowns.abilityCooldown.put(uuid, 10);
                    }
    And here is the code for the cooldown:

    Code (Java):
    public class Cooldowns {
        public static HashMap<UUID, Integer> abilityCooldown = new HashMap<>();
        public static int abilityCooldownTime = 10;
     
        public static void runnableAbilityCooldown() {
            (new BukkitRunnable() {
                public void run() {
                  if (Cooldowns.abilityCooldown.isEmpty())
                    return;
                  for (UUID uuid : Cooldowns.abilityCooldown.keySet()) { // THIS IS SUPPOSEDLY THE LINE THAT CREATES THE ERROR
                    int timeleftAbility = ((Integer)Cooldowns.abilityCooldown.get(uuid)).intValue();
                    if (timeleftAbility <= 0) {
                        Cooldowns.abilityCooldown.remove(uuid);
                      continue;
                    }
                    Cooldowns.abilityCooldown.put(uuid, Integer.valueOf(timeleftAbility - 1));
                  }
                }
              }).runTaskTimer((Plugin)SuperWeapons.plugin, 0L, 20L);
        }
    }
     
  2. You remove keys from the hashmap while looping over it which is why it throws the exception. You have to remove them outside of the for loop
     
    • Like Like x 1
  3. Hello,

    You can not modify a collection while iterating over it:
    Code (Java):
    for (UUID uuid : Cooldowns.abilityCooldown.keySet()) { // Iterating
        Cooldowns.abilityCooldown.remove(uuid); // modifying it while iterating: ConcurrentModificationException
        Cooldowns.abilityCooldown.put(uuid, Integer.valueOf(timeleftAbility - 1)); // modifying it while iterating: ConcurrentModificationException
    You can use an Entry Iterator to remove/modify the current iterated value.
     
    #3 Andross, Mar 23, 2021
    Last edited: Mar 23, 2021
    • Like Like x 1
  4. Thanks for your reply! I tried changing the HashMap to a ConcurrentHashMap and I don't seem to get the error anymore. Do you think that actually solved it or is it a bad solution? If so, could you show me how I would use an Entry Iterator? I've never done that myself.
     
  5. This is a really bad solution.

    You can use an Entry Iterator that way:
    Code (Java):
    final Iterator<Map.Entry<UUID, Integer>> it = map.entrySet().iterator();
    while (it.hasNext()) {
       final Map.Entry<UUID, Integer> entry = it.next();
       // Do your checks
       // entry.setValue - to modify the value of the entry;
       // it.remove(); - to remove the current entry from the map;
    }
     
    • Winner Winner x 1
  6. Like this? Or did you mean for it to replace the for loop? Because I still need to get the current uuid in order to remove or modify it
    Code (Java):
    for (UUID uuid : Cooldowns.abilityCooldown.keySet()) {
                        int timeleftAbility = ((Integer)Cooldowns.abilityCooldown.get(uuid)).intValue();
                        final Iterator<Map.Entry<UUID, Integer>> it = abilityCooldown.entrySet().iterator();
                        while (it.hasNext()) {
                            final Map.Entry<UUID, Integer> entry = it.next();
                            if (timeleftAbility <= 0) {
                                it.remove();
                                continue;
                            }
                            entry.setValue(timeleftAbility - 1);
                        }
                    }
     
  7. You do not need to iterate (loop) into the keySet as you already doing it using the Entry Iterator.
    You can get the UUID using entry.getKey
     
    • Useful Useful x 1
  8. That seems to have solved it. Thank you!