Threads are native mechanisms, whereas coroutines are user-level abstractions, which use threads and actually make better use of them.
Key differences I can point out:
Thread is a different mechanism, which is linked to the native thread
of OS. This is the reason why creating hundreds/thousands of threads
is impossible - thread consumes a lot of OS' memory. Coroutine is a
user-level abstraction of some worker which does not use excessive
amount of memory, since it is not linked to native resources and use
resources of JVM heap.
Thread gets blocked instead of suspending and dispatching the job to
another thread.
Thread cannot be used until its work completes.
Coroutine is a user-friendly abstraction which allows you to reuse thread's resources to execute suspending functions. Mechanism is following: suspending function is called from a coroutine and dispatched on a specific thread. While it suspends, thread resources can be used to execute another suspending function.
To get better understanding of how coroutines work in combination with threads, you can read my answer to my own question, which is also a source for this answer.