Imagine we have three tasks at hand to complete: mop the floor (T1), make a pancake (T2), and write a report (T3). Since we are pretty bad at working multiple tasks at the same time, we can either complete one task at a time or switching between them until all are completed. How do we execute them is contingent upon the urgency of each task, how long each task takes time, and the resources that we have. Thus, the order must reflect their priority.
Each task can be further broken down into chunks of independent workable time unit (thread). To complete the tasks faster, we can scale vertically by hiring people to work on some tasks. Thus, the tasks can be performed in parallel fashion (see Table below).
|Performer||Task sequence (“-t-“ represents time unit)|
cost: 15, time: 10t
|T3 -t-t-t-t-t- T2 -t-t-t- T1 -t-t-|
|Single context switch
cost: 15, time: 10t
|T3 -t- T2 -t- T1 -t- T3 -t- T2 -t- T1 -t- T2 -t- T3 -t-t-t-|
|2 people||T3 -t-t-t-t-t- (person 1)|
|cost: 20, time: 5t||T2 -t-t- T1 -t- (person 2)|
|3 people||T3 -t-t-t-t-t- (person 1)|
|cost: 30, time: 5t||T2 -t-t- (person 2)|
|T1 -t- (person 3)|
Likewise, computers process tasks using available number of processors and other supporting hardware resources (e.g., RAM capacity and bus speed). Single performer is equivalent to a single core computers, two persons are equivalent to dual-cores computers, etc. The more cores, the more capability in processing threads parallelly (multiprocessing), hence, better paralellism (at hardware level).
At operating system (software) level, each single core can context switch between tasks very fast, such that it seems to run threads parallelly (multithreading, multiprogramming). This is equivalent to single context switch in the example above.
To be clear, parallelism executes threads concurrently and parallely at the same time.
Thread 1 -t-t-t-
Thread 2 -t-t-t-t-t-
Threads can be executed in concurrently in a context switching fashion without parallelism.
Thread 1 -t| -t| -t| (| = context switch)
Thread 2 -t-t| -t-t| -t-
In short, hardware parallelism requires concurrency (at software level) whereas concurrency doesn’t guarantee parallelism.
The main question as software engineers is how we can optimize tasks execution. This can be detailed into several sub-questions such as: how we can get results from all tasks in a shortest time? should tasks run successfully together or fail independently? if a task fails, how we handle it and what default result we expect?
This is where asynchronous programming comes in to enable us, developers, to leverage and take advantages of parallelism. So, how can we do that in Java? I’ll continue this in the next part.