Indeed, an instance of ScopesHashModel is not thread-safe. But as I looked through the source I have concluded that it doesn't needs to be thread-safe.
Here is what I did. I opened the call hierarchy for the constructor of ScopesHashModel. This gets called only from FreemarkerManager.buildScopesHashModel(), and that from FreemarkerManager.buildTemplateModel(). From here there are two paths:
1. FreemarkerResult.createModel(), then FreemarkerResult.doExecute(). Here the ScopesHashModel is assigned to a local variable, and after exiting the method, the ScopesHashModel will be garbage collected.
2. FreemarkerTemplateEngine.renderTemplate(). Here the situation is similar, the ScopesHashModel is a local variable and will be garbage collected.
So it seems that an instance of ScopesHashModel is not used by multiple threads. For every struts2 tag rendering, a new instance is created. An other field of this class, unlistedModels is also a simple HashMap, so the class is not totally thread-safe even in it's current form.
But it is true that there may be a plugin or any code using this class in a way that multiple threads operate on the same instance. The base class, freemarker.template.SimpleHash is a bit more thread safe, javadoc says: "This class is thread-safe if you don't call the put or remove methods after you have made the object available for multiple threads.".
So I have created another patch that uses a ConcurrentHashMap. For this the code had to be modified also, because ConcurrentHashMap (unlike HashMap) does not handle null values.