With the recent fast adoption of Internet and cloud environments, the focus of software development shifted from building single user type applications, into a realm of constructing software capable of handling numerous local or remote users. The core benefit of multi-user applications lies in their capacity to tolerate synchronized access from simultaneous users. Other advantages often include the ability to support different types of desktop and mobile devices using a variety of operating systems.
“A multi-user application is a computer program run on a computer system that allows multiple participants that are geographically dispersed to interact with the computer program.” (Guy, Van Datta, and Fernandes, 2004).
Java Multi-User Application
While the benefits mentioned above allow an ever-growing number of users to participate in real-time collaboration, today’s software architects must consider issues such as management and synchronization of multiple data sending or receiving threads, and associated error handling.
Let’s briefly consider the construction of a multi-user Java-based application similar to Word 365 in which multiple simultaneous users can read the same document, but only one at the time has the permission to amend it.
Java is a multi-threaded programming language, and that means we can implement the application by using Java’s thread model. An application diagram (Jarosciak, 2018a) illustrates such a multithreaded application, which creates a separate synchronized thread for each user. As we can see, the multi-threaded Java program can contain multiple threads that run alongside each other and where each thread handles its job. This practice promotes a well-organized resource usage, which is a crucial advantage of using application thread instead of the new application process (a new thread requires fewer resources).
Jarosciak, J. (2018a) Java Multi-User Threading. Unpublished figure
That said, let’s look at how we can create a thread in Java. Java thread model supports two ways of creating a thread, “First, a subclass of the class Thread can be instantiated to create threads. The other way to create a thread is to declare a class that implements the standard interface Runnable.” (Li, 2005).
The first option that uses a subclass of the class Thread and defines a single ‘run’ method that contains the code that executes in the thread is illustrated in the diagram below (Jarosciak, 2018b).
Jarosciak, J. (2018b) Java Thread Subclass. Unpublished figure
The second option that segregates the Runnable task from the Thread object is used in more complex applications. The diagram (Jarosciak, 2018c) shows how the “idiom which employs a Runnable object, is more general because the Runnable object can subclass a class other than Thread” Docs.oracle.com. (2018)
Jarosciak, J. (2018c) Java Runnable Thread. Unpublished figure
In the Word 365 like the application, we would use a Runnable object to create a separate thread for each user. However, the access to the document must also consider the issues tied to concurrency, such as thread synchronization. The diagram (Jarosciak, 2018c) demonstrates how even in a simple process such as incrementing a counter, the problem of thread concurrency can arise.
To resolve the concurrency issues, Java created a synchronization method named ‘synchronized‘ method, that can be utilized in threads, as shown in the diagram (Jarosciak, 2018d).
Jarosciak, J. (2018d) Java Synchronized Method. Unpublished figure
Thread vs. Process
The easiest way to explain the difference is that the thread always runs in the setting of a process. So we could simply say that it’s more economical to add threads to a process, rather than create multiple application processes for each task that application needs to run. According to Msdn.microsoft.com (2018), “A thread is the basic unit to which the operating system allocates processor time. A thread can execute any part of the process code, including parts currently being executed by another thread.”
Another, more visual and detailed explanation comes from Randu.org (2018) and it’s illustrated in Figure 1 that shows that a computer program becomes a process at the point when it is loaded from a store into the computer’s memory and when CPU(s) starts the execution. When that happens, computer memory keeps track of some of the indispensable information such as currently executed instruction(s), file handles, registers, etc. That said, from the point of view of resource usage, creating processes is expensive.
So the answer to a conservative usage of resources is to use a single process per application and use a multithreaded model instead (something Java supports). In the context of a multi-threaded application, a thread is a series of instructions inside the program that are executed independently of other code. The Figure 1 explains how threads are contained inside the process, and also how they are contained in the same process address space.
Other than the smart usage of computer resources, the other main benefit is that all the information present in the memory available to the process executing threads – is shared by the threads. As we can immediately guess, this would not be the case if the application designers decided to create multiple application processes instead of multiple threads inside the application process.
Figure 1 – What is a Thread? Randu.org. (2018)
Threads are lightweight because they use a smaller unit of CPU and memory utilization. For those reasons alone, the multithreading is a very efficient way of designing an application. Programs that use threads are typically much faster. The benefit as we’ve just learned is also that threads can access the memory area used by another thread within a process.
The thread class in Java, however, comes with some disadvantages as well. Primarily they are connected to a complexity overhead when architecting a multithreaded application. Primarily we’re talking about making sure that synchronization among threads is properly designed and that context switching does not create memory issues/errors. Another issue is related to block threads that may hold the computer resources while waiting for other thread to complete the job, so in applications that use tens or hundreds of threads, this can actually lead to inefficient use of computer resources.
Multi-Threading Issues in Java
“Developing programs with simultaneously executed tasks is difficult and error-prone, requiring a broad knowledge of the programming language to achieve a correct and robust code.” (Oaks and Wong, 1999, p. 11)
There are many issues tied to multi-threading and concurrency in Java. The most common among many are the deadlocks and race conditions.
Deadlock situation results in threads being forever blocked. The thread deadlock situations are relatively easy to diagnose. There are a couple of options available to us straight from Oracle as a recommended way to deal with deadlock detection.In my opinion, the simplest is to observe the java thread dump of the application, which can be created by using either of the following two tools:
The jstack Utility
“The jstack command-line utility attaches to the specified process or core file and prints the stack traces of all threads that are attached to the virtual machine, including Java threads and VM internal threads, and optionally native stack frames. The utility also performs deadlock detection.” Docs.oracle.com. (2018). Thread dumps can also be obtained programmatically using the Thread.getAllStackTraces method.
Another more visual way is to use Oracle’s VisualVM, which has an option to create a thread dump. Figure 2 and Figure 3 shows the options available for taking the thread dump as well as where in the application we can examine the output of the thread dump. Once the output is available, we can use the stack trace to diagnose issues such as deadlocks that may be slowing our application or completely stopping the processing.
Figure 2 – Docs.oracle.com – VisualVM (2018)
Figure 3 – Docs.oracle.com – VisualVM (2018)
Once we have the thread dump, we can analyze the deadlock by searching for all threads that are not moving from the BLOCKED state, and we look at the resource IDs because they will usually hold the lock on the object. This way we can discover which thread is holding the lock, blocking other threads.
However, VisualVM also allows us to look at the state of any thread in our application, such as illustrated in the screenshot below (Figure 4).
Figure 4 – Docs.oracle.com – VisualVM Detail (2018)
“Race conditions can cause serious errors in programs such as, security violations, data corruption, and crashes.” (Pierce and Chau, 2007).
As we can see, the race conditions a somewhat of a harder circumstance to deal with in Java. The issue is typically silent and irregular causing the Undetermined Behavior as well as often quite irregular results.
But let’s first explain what it is. A perfect explanation was provided by one of the StackOverflow editors: “A race condition occurs when two or more threads can access shared data, and they try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, you don’t know the order in which the threads will attempt to access the shared data.” (Stackoverflow.com, 2018)
As explained, it’s not so easy to detect a race condition. There are several patents I found that deal with the issue of race detection, such as US Patent 7,028,119, 2006 and US Patent 7,174,554, 2007, but these are more of a theoretical concept.
One of the tools available to Java developers that can assist with the race condition detection is called FindBugs, which is a program that applies static analysis to scan the application for bugs in Java code. But static code analysis is not fault-proof and these methods can only go so far in detecting issues such as race conditions are time-sensitive.
As we can see, it’s quite hard to create a safe multi-threaded application and the degree of possible error situation rises linearly with the degree of application complexity.
We’ve successfully demonstrated a process of creation of a multithreaded synchronized Java application.
Guy, C. H., Van Datta, G. A., & Fernandes, J. A. (2004). U.S. Patent No. 6,826,523. Washington, DC: U.S. Patent and Trademark Office.
Li, L. (2005). Implementing the p-Calculus in Java. Journal of Object Technology, 4(2), 157-178.
Docs.oracle.com. (2018). Defining and Starting a Thread (The Java™ Tutorials > Essential Classes > Concurrency) . [online] Available at: https://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html [Accessed 25 Feb. 2018].
Msdn.microsoft.com. (2018). Processes and Threads (Windows). [online] Available at: https://msdn.microsoft.com/en-us/library/windows/desktop/ms684841(v=vs.85).aspx [Accessed 26 Feb. 2018].
Randu.org. (2018). Multithreaded Programming (POSIX pthreads Tutorial). [online] Available at: https://randu.org/tutorials/threads/ [Accessed 26 Feb. 2018].
Docs.oracle.com – jstack. (2018). The jstack Utility. [online] Available at: https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr016.html [Accessed 27 Feb. 2018].
Docs.oracle.com – VisualVM. (2018). Java VisualVM. [online] Available at: https://docs.oracle.com/javase/7/docs/technotes/guides/visualvm/applications_local.html [Accessed 27 Feb. 2018].
Docs.oracle.com – VisualVM Detail. (2018). Java VisualVM. [online] Available at: https://docs.oracle.com/javase/7/docs/technotes/guides/visualvm/threads.html [Accessed 27 Feb. 2018].
Pierce, K. B., & Chau, H. Y. (2007). U.S. Patent No. 7,174,554. Washington, DC: U.S. Patent and Trademark Office.
Stackoverflow.com. (2018). What is a race condition?. [online] . Available at: https://stackoverflow.com/questions/34510/what-is-a-race-condition [Accessed 27 Feb. 2018].
Oaks, S., & Wong, H. (1999). Java threads. Sebastopol, CA: O’Reilly Media. 2nd edition.