It's by design, because the fields are used to build the query with. Everything that's used to build the query with isn't thread safe as it's augmented along the way to make query generation possible, and the philosophy is that the input to produce the query is created just for that purpose. This means that you can't use them in a cross-thread manner. In general this isn't an issue as the objects are created for just the purpose of creating the query, and then tossed away. Thread safety is something that's always 'false' unless it's specified as such.
Creating field objects is fast, it shouldn't be noticeable. If you want to re-use them, you can call EntityFields2.Clone() to create a deep clone of the fields object (it clones the expressions as well etc.)
However Clone() doesn't use a lock so in the end you still need to use a lock around the Clone() call. I haven't measured whether that's faster than re-creating it, but for a normal set of fields for e.g. an entity or typed list, I'd assume it's (a bit) slower.
So I'd first try to simply re-create the fields every time and measure whether that's significantly slower and if so, cache it and use a lock and Clone() it. Using ThreadSafe doesn't work in a web-oriented scenario as requests could hop threads.
As a db call is a magnitude slower than recreating a large set of fields, I wouldn't worry about recreating it. Only cache if profiling suggested it's a bottleneck and caching would fix things. (we already cache a great deal of that for you btw: the field info objects are already cached and re-used as it's static data hence recreating the fields is fast and likely not a bottleneck).