the second parameter of setTimeout takes in the minimum time after which the callback function (first argument) is to be pushed onto the event loop, which is nothing but a queue for callback functions. This queue is consumed to actually start execution.
Once first setTimeout is encountered, function is pushed onto someplace outside and is told to wait for 3 seconds before re-entering in single threaded world. Same happens for second timeout function however it has to wait only 1 sec. The entry point to this single threaded world is the callback queue. JS Engine continues normal execution as if settimeout execution is done with. Now once 1 sec expires, the function of second timeout is pushed onto the queue and waiting to be executed. If the call stack is clear at that point of time then the function goes for processing(assuming it was the first member of the queue) and "4" is printed. Now if 3 sec has not passed in this time, the function of first timeout is still waiting someplace outside. Once 3 seconds pass, the callback function enters the queue and since the call stack is clear, it executes and "2" is printed.
Now browser has access to multiple threads from the OS (though providing only a single threaded environment for JS execution). These setTimeouts are processed by another thread behind the scene.
There is a video by Philips Robert that beautifully explains the concepts of queue and event loop that lead to 'asynchronousness' of single threaded javascript.