On a platform that supports multi-threading the threads run in parallel.
That means that multiple threads can run at the same time.
This depends on the numbers of cores the CPU makes available.
If the programmer insists on running more threads than the CPU has cores then the threads will be time-sliced.
How much time each thread gets depends on the priority of that thread.
A thread can also choose to yield, which means that it will transfer execution time to another thread before its time slice is up.
If a thread does not yield the scheduler (which is part of the operating system) will pause the thread and force another thread to run.
An interrupt handler interrupts the main program. That means that the state of the thread is saved and execution is paused.
The interrupt handler is executed and when the interrupt handler yields the state of the main program is restored and execution of the main program resumes.
In many systems the interrupt handler is assigned a standard interruption schedule, e.g. 50 times a second.
The interrupt is always given preference over the main program, so if the interrupt takes up a lot of time the main program will get very little or no time to run. The interrupt gets scheduled no matter what. The main program gets whatever time is left over.
An interrupt handler is not subject to a scheduler, although possibly the interrupt can be interrupted by another interrupt.
In contrast, threads (insofar as threads outnumber CPU cores) are time sliced. Every thread gets the same amount of time. If there is a high priority thread it may get more time or get scheduled more often, but the scheduler makes sure every thread does get a time slot now and then.
Interrupts don't play nice
Because an interrupt handler will get prioritized over the main execution it is unwise to treat it like a thread. If you do heavy calculation in an interrupt handler your main program will never run!
This is made worse by the fact that interrupts are usually scheduled to run many times a second.
There is an alternative to abusing the interrupt handler for multi-threading.
You can use fibers on Arduino.
Fibers are cooperative threads, that means that a single thread will run until it yields.
After a yield the next fiber in line will run.
As long as all fibers yield in a cooperative manner this works very much like real pre-emptive threads.
If one fiber does not yield, none of the other fibers will get any running time.
This is the multi-tasking mechanism that was used in Windows 3.x
Just to make things confusing libraries that support this behavior talk about threading, not fibers.
Note that threading is impossible on Arduino, so these misnamed 'threads' are really fibers.