
Explain single threaded versus multi-threaded apps using serial execution, parallel tasks, and examples like a text editor, web apps, and an audio player in Java.
Multi-threaded applications normally do parallel processing of tasks where as single threaded applications do one task at a time i.e. If there are two tasks such as T1, T2 they are executed in serial order i.e T1 after T2 or T2 after T1, where as multi threading enables us to execute them simultaneously i.e. T1 along with T2. We can choose single threaded applications when parallel processing is not required example use cases such as simple text editor.
True parallelism is achieved by assigning tasks to individual CPUs. Where as if there is one CPU and you want to perform multiple tasks in parallel, then CPU will be shared across those tasks for some stipulated time interval, which stands for interleaved execution, and this way of executing the tasks is known as logical parallelism or psuedo parallelism.
You can create a thread by instantiating Thread class and submit it for execution using the start method. In order to perform a task using thread, you can subclass the Thread class and inside the run method you can perform the corresponding task. One other approach is to implement Runnable interface and submit it for Thread class instance to run it. Later approach is better because sub-classing the thread class means you are extending the Thread functionality which is not the case. Also in case of runnable tasks we can utilise an existing Thread object, so that the same Thread is used for executing multiple tasks.
File copy is an IO intensive activity. Take for example you need to copy 100 files, if you do it in serial manner you are not effectively using the CPU and IO resources. Instead you can make it parallel through Java threads and get optimal performance.
Thread creation is a costly activity as it includes creating a separate execution context, stack etc.. Hence we should refrain from creating too many threads for an application. And also creating a thread for each task is not a good idea, instead we can create a pool of threads and effectively utilise them in executing all the task. This could be achieved using ExecutorService in Java. Use the execute method of the ExecutorService to submit a Runnable task, if a thread is available in the pool then it assigns this task to the thread otherwise the task is added to the blocking queue and is kept till a thread is available.
stop() method of the Thread class could be used to stop the thread in the middle. But this is the dangerous thing to do as it leaves the system in inconsistent state, because we are not giving the opportunity to the thread to rollback or reverse the actions that it has taken. And hence the stop method is deprecated.
Correct approach would be to interrupt() the thread and then it is up to the thread to consider it. A thread can check if it was interrupted or not using interrupted() method. If interrupted() we can design the thread in a way that it reverses the actions it has performed and then stop.
sleep() method of the thread class is used to block the thread for the given time interval in milliseconds.
Explore the six thread states—new, runnable, timed waiting, waiting, blocked, and terminated—and how yield, wait, and join govern CPU time and thread readiness.
Unlike Runnable, Callable interface allows us to create an asynchronous task which is capable of returning an Object. Create a Callable task and submit it for ExecutorService which returns the Future object. We can use this Future object to get the result once the task is completed.
Enable parallel pattern search in a folder using a fixed thread pool, callable tasks, and futures to gather per-file pattern results into a map, reducing from serial to parallel execution.
When an object is modified through multiple threads we need to synchronize the operation to make it threadsafe or re-entrant. If it not taken care then it might lead to inconsistent results.
To make a method re-entrant we might declare it as synchronized. When a method is synchronized the corresponding object is locked before a thread can enter into the method. This can solve concurrency problems to some extent but it is not a good solution.
Instead of making a method synchronized, we can use the synchronized block which allows us to retain the lock only for a specific portion of code instead of the entire method. This is at times a better solution as synchronized method doesn't solve a class of problem where multiple method calls need to be atomic instead of one single method.
ReentrantReadWriteLock allows us to obtain two different kinds of locks,
read write locks are preferred over synchronized methods due to shared read and their ability to go beyond a method with lock acquired.
Explore how threads signal each other with wait and notify, requiring synchronized locks, and learn the differences between wait and wait with milliseconds, notify, and notifyAll.
Explore the fork join framework in Java 7, featuring ForkJoinPool and ForkJoinTask with work stealing, common pool usage, and recursive splitting into subtasks (recursive task) or actions (recursive action).
Multi threading in Java is the most essential feature that each and every Java developer should understand and be able to confidently apply it in solving complex programming problems where concurrent execution is a must. With parallel processing there comes a risk, i.e. shared mutability and the ability of the developer to solve concurrency problems.
Thanks to the Java programming language that it makes the mighty multi-threaded application development a cake walk, if you assimilate few keys or core concepts you will be able to confidently design multi threaded applications with ease.
To design multi threaded applications developers need to look at it through a different perspective i.e. change the thinking from serial to parallel and it requires some effort. Keeping this in mind this course contains the essential lectures which can guide you in this regard.
This course was designed keeping the above points in mind and will help you understand these complex problems through simplified and easy to understand examples.
Course contents include
Understanding Threads, Priorities, States, Daemon Threads.
Thread synchronization using synchronized blocks and locks.
Key components of java.util.concurrent package including ForkJoinPool and ForkJoinTask(s).
Mock HttpServer to explain how HTTP works and simulate how a typical web server responds to the client requests.
ThreadLocal
Introduction to distributed locks
An overview of how to build scalable applications using Messaging Queues.
Introduction to Lambdas and Streams (Coming up)