Just from my experience with Objective-C which has had automatic reference counting for years:
Reference counting isn't really about resources. It is about life times of objects. An object is created, a reference is stored somewhere, and the reference count is set to 1. Then various bits of code need a reference to the same object, every time the reference counter is increased. Or a bit of code stops needing the reference, then the reference counter is decreased. In Objective-C, when the reference counter becomes zero, the object is dead (it might not know it yet, but it is dead). The reference counting code itself starts destroying the object. At the same time, the reference counter cannot be changed anymore once it has reached zero, so unlike Java, once the reference count is 0, the object will go.
Now increasing and decreasing the reference count from two different threads is not a trivial problem. In Objective-C, a location where a reference is stored is marked as "atomic" or "non-atomic". The difference is that the actual act of increasing or decreasing the reference count works correctly when called from multiple threads if the variable is atomic, while it is not guaranteed to work if the variable is non-atomic and could crash or misbehave in some way.
However, "not crashing" and being atomic is not really enough. Imagine two threads, one decreases the reference count, one increases it, at precisely the same time, as close as possible, and we start with a reference count of 1. If the decrease happens first, then the object will be released, and by the time we try to increase the reference count, it is gone. If the increase happens first, then the reference count is set to 2, one nanosecond later is set to 1, and the object is still alive and kicking. So in this situation there are two very different outcomes, both possible and legal. That just cannot be right.
So in Objective-C, the "atomic" variant was used very, very rarely. Because in those cases where "atomic" avoided undefined behaviour on the code level, you still have unpredictable behaviour on the application level, which will not crash, but is most likely a bug in your code.
(In Swift, there is no distinction between atomic and non-atomic anymore. There are some tiny changes to the ARM processors that are very commonly used, that make atomic changes very fast if an atomic variable hasn't every actually been touched by two different cores. So in cases where "atomic" would be not needed, the atomic code is very, very fast. And in cases where it's needed, well in those cases, it is needed.
& _
) available to it".