Index: src/org/openstreetmap/josm/plugins/PluginHandler.java
===================================================================
--- src/org/openstreetmap/josm/plugins/PluginHandler.java	(revision 16850)
+++ src/org/openstreetmap/josm/plugins/PluginHandler.java	(working copy)
@@ -277,7 +277,7 @@
     /**
      * All installed and loaded plugins (resp. their main classes)
      */
-    static final Collection<PluginProxy> pluginList = new LinkedList<>();
+    static final Collection<PluginProxy> pluginList = Collections.synchronizedCollection(new LinkedList<>());
 
     /**
      * All installed but not loaded plugins
@@ -322,8 +322,10 @@
      * @since 10982
      */
     public static List<PluginInformation> getPlugins() {
-        return pluginList.stream().map(PluginProxy::getPluginInformation)
-                .sorted(Comparator.comparing(PluginInformation::getName)).collect(Collectors.toList());
+        synchronized (pluginList) {
+            return pluginList.stream().map(PluginProxy::getPluginInformation)
+                    .sorted(Comparator.comparing(PluginInformation::getName)).collect(Collectors.toList());
+        }
     }
 
     /**
@@ -598,7 +600,13 @@
         MainApplication.worker.submit(() -> {
             // Build list of plugins to download
             Set<PluginInformation> toDownload = new HashSet<>(pluginInfoDownloadTask.getAvailablePlugins());
-            toDownload.removeIf(info -> !missingRequiredPlugin.contains(info.getName()));
+            toDownload.removeIf(info -> !missingRequiredPlugin.contains(info.getName())
+                    && !missingRequiredPlugin.contains(info.provides) || !info.isForCurrentPlatform());
+            // Remove duplicates
+            Set<String> possibleDuplicates = toDownload.parallelStream()
+                    .collect(Collectors.groupingBy(i -> i.provides == null ? i.getName() : i.provides, Collectors.counting()))
+                    .entrySet().stream().filter(e -> e.getValue() > 1).map(Map.Entry::getKey).collect(Collectors.toSet());
+            toDownload.removeIf(i -> possibleDuplicates.contains(i.getName()) || possibleDuplicates.contains(i.provides));
             // Check if something has still to be downloaded
             if (!toDownload.isEmpty()) {
                 // download plugins
@@ -692,8 +700,10 @@
 
         // Add all plugins already loaded (to include early plugins when checking late ones)
         Collection<PluginInformation> allPlugins = new HashSet<>(plugins);
-        for (PluginProxy proxy : pluginList) {
-            allPlugins.add(proxy.getPluginInformation());
+        synchronized (pluginList) {
+            for (PluginProxy proxy : pluginList) {
+                allPlugins.add(proxy.getPluginInformation());
+            }
         }
 
         // Include plugins that have been processed but not been loaded (for javafx plugin)
@@ -872,10 +882,12 @@
                             continue DEPENDENCIES;
                         }
                     }
-                    for (PluginProxy proxy : pluginList) {
-                        if (isDependency(proxy.getPluginInformation(), depName)) {
-                            cl.addDependency(proxy.getClassLoader());
-                            continue DEPENDENCIES;
+                    synchronized (pluginList) {
+                        for (PluginProxy proxy : pluginList) {
+                            if (isDependency(proxy.getPluginInformation(), depName)) {
+                                cl.addDependency(proxy.getClassLoader());
+                                continue DEPENDENCIES;
+                            }
                         }
                     }
                     Logging.error("unable to find dependency " + depName + " for plugin " + info.getName());
@@ -1232,9 +1244,11 @@
      * @return The plugin of the specified name, if installed and loaded, or {@code null} otherwise.
      */
     public static Object getPlugin(String name) {
-        for (PluginProxy plugin : pluginList) {
-            if (plugin.getPluginInformation().name.equals(name))
-                return plugin.getPlugin();
+        synchronized (pluginList) {
+            for (PluginProxy plugin : pluginList) {
+                if (plugin.getPluginInformation().name.equals(name))
+                    return plugin.getPlugin();
+            }
         }
         return null;
     }
@@ -1247,9 +1261,11 @@
      * @since 12323
      */
     public static PluginClassLoader getPluginClassLoader(String name) {
-        for (PluginProxy plugin : pluginList) {
-            if (plugin.getPluginInformation().name.equals(name))
-                return plugin.getClassLoader();
+        synchronized (pluginList) {
+            for (PluginProxy plugin : pluginList) {
+                if (plugin.getPluginInformation().name.equals(name))
+                    return plugin.getClassLoader();
+            }
         }
         return null;
     }
@@ -1260,8 +1276,10 @@
      * @param downloadSelections list of bounding box selectors
      */
     public static void addDownloadSelection(List<DownloadSelection> downloadSelections) {
-        for (PluginProxy p : pluginList) {
-            p.addDownloadSelection(downloadSelections);
+        synchronized (pluginList) {
+            for (PluginProxy p : pluginList) {
+                p.addDownloadSelection(downloadSelections);
+            }
         }
     }
 
@@ -1271,8 +1289,10 @@
      */
     public static Collection<PreferenceSettingFactory> getPreferenceSetting() {
         Collection<PreferenceSettingFactory> settings = new ArrayList<>();
-        for (PluginProxy plugin : pluginList) {
-            settings.add(new PluginPreferenceFactory(plugin));
+        synchronized (pluginList) {
+            for (PluginProxy plugin : pluginList) {
+                settings.add(new PluginPreferenceFactory(plugin));
+            }
         }
         return settings;
     }
@@ -1489,13 +1509,15 @@
 
         // remember the error position, as multiple plugins may be involved, we search the topmost one
         int pos = stack.size();
-        for (PluginProxy p : pluginList) {
-            String baseClass = p.getPluginInformation().className;
-            baseClass = baseClass.substring(0, baseClass.lastIndexOf('.'));
-            for (int elpos = 0; elpos < pos; ++elpos) {
-                if (stack.get(elpos).getClassName().startsWith(baseClass)) {
-                    pos = elpos;
-                    err = p;
+        synchronized (pluginList) {
+            for (PluginProxy p : pluginList) {
+                String baseClass = p.getPluginInformation().className;
+                baseClass = baseClass.substring(0, baseClass.lastIndexOf('.'));
+                for (int elpos = 0; elpos < pos; ++elpos) {
+                    if (stack.get(elpos).getClassName().startsWith(baseClass)) {
+                        pos = elpos;
+                        err = p;
+                    }
                 }
             }
         }
@@ -1557,11 +1579,13 @@
      */
     public static Collection<String> getBugReportInformation() {
         final Collection<String> pl = new TreeSet<>(Config.getPref().getList("plugins", new LinkedList<>()));
-        for (final PluginProxy pp : pluginList) {
-            PluginInformation pi = pp.getPluginInformation();
-            pl.remove(pi.name);
-            pl.add(pi.name + " (" + (pi.localversion != null && !pi.localversion.isEmpty()
-                    ? pi.localversion : "unknown") + ')');
+        synchronized (pluginList) {
+            for (final PluginProxy pp : pluginList) {
+                PluginInformation pi = pp.getPluginInformation();
+                pl.remove(pi.name);
+                pl.add(pi.name + " (" + (pi.localversion != null && !pi.localversion.isEmpty()
+                        ? pi.localversion : "unknown") + ')');
+            }
         }
         return pl;
     }
Index: src/org/openstreetmap/josm/plugins/PluginListParser.java
===================================================================
--- src/org/openstreetmap/josm/plugins/PluginListParser.java	(revision 16850)
+++ src/org/openstreetmap/josm/plugins/PluginListParser.java	(working copy)
@@ -94,9 +94,11 @@
         try {
             if (name != null) {
                 PluginInformation info = createInfo(name, url, manifest);
-                for (PluginProxy plugin : PluginHandler.pluginList) {
-                    if (plugin.getPluginInformation().name.equals(info.getName())) {
-                        info.localversion = plugin.getPluginInformation().localversion;
+                synchronized (PluginHandler.pluginList) {
+                    for (PluginProxy plugin : PluginHandler.pluginList) {
+                        if (plugin.getPluginInformation().name.equals(info.getName())) {
+                            info.localversion = plugin.getPluginInformation().localversion;
+                        }
                     }
                 }
                 ret.add(info);
Index: src/org/openstreetmap/josm/plugins/ReadLocalPluginInformationTask.java
===================================================================
--- src/org/openstreetmap/josm/plugins/ReadLocalPluginInformationTask.java	(revision 16850)
+++ src/org/openstreetmap/josm/plugins/ReadLocalPluginInformationTask.java	(working copy)
@@ -169,13 +169,15 @@
     }
 
     protected void analyseInProcessPlugins() {
-        for (PluginProxy proxy : PluginHandler.pluginList) {
-            PluginInformation info = proxy.getPluginInformation();
-            if (canceled) return;
-            if (!availablePlugins.containsKey(info.name)) {
-                availablePlugins.put(info.name, info);
-            } else {
-                availablePlugins.get(info.name).localversion = info.localversion;
+        synchronized (PluginHandler.pluginList) {
+            for (PluginProxy proxy : PluginHandler.pluginList) {
+                PluginInformation info = proxy.getPluginInformation();
+                if (canceled) return;
+                if (!availablePlugins.containsKey(info.name)) {
+                    availablePlugins.put(info.name, info);
+                } else {
+                    availablePlugins.get(info.name).localversion = info.localversion;
+                }
             }
         }
     }
