is not an atomic operation, it is syntactic sugar for:
counter = counter + 1;
And because you have multiple threads accessing the same memory location at the same time, sometimes one thread will get in while another has loaded the value from memory, but not incremented it yet, while a third had incremented the value but not stored it yet, and a fourth has just stored it and is about to load it again. With the silly number of thread you are creating and limited number of cores to run them on, it's pretty much guaranteed that tis is going to happen a lot, and that means that two runs are very unlikely to produce the same result.
Basically, your code is described as "not thread safe" and unpredicable results are a feature of a lack of thread safety.
If you want reliable results, you would have to institute either locking or a mutex, and that'll effectively slow your app down to below the point at which a single thread would have done the same thing!
And remember: creating more threads than you have available cores is likely t slow your processing down, rather than speed it up: threading is not a "magic bullet" for performance improvement, it's something which has to be thought about very carefully if it's not going to both complicate your code and slow it down!