Java Thread Synchronization <br />When two or more threads need to share resources, they need some way to determine that the resource is occupied by only one thread at a certain moment. The process of achieving this goal is called synchronization. As you can see, Java provides unique, language-level support for this.
The key to synchronization is the concept of pipeline (also called semaphore). A mutex is a mutex locked object, or a mutex. At a given time, only one thread can obtain the management process. When a thread needs to be locked, it must enter the pipeline. All other threads trying to enter the locked pipe must be suspended until the first thread exits the pipe. These other threads are called waiting management processes. A thread with a management process can enter the same management process again if it wants.
If you have used synchronization in other languages such as C or C++, you will know that it is a bit weird to use. This is because many languages do not support synchronization themselves. Instead, for synchronous threads, programs must utilize operating system source language. Fortunately, Java implements synchronization through language elements, and most of the synchronization-related complexities are eliminated.
You can synchronize the code in two ways. Both include the use of synchronized keywords, and the following are the two methods.
Using the synchronization method
Synchronization in Java is simple because all objects have their corresponding implicit procedures. Entering the management process of a certain object is to call the method modified by the synchronized keyword. When a thread is inside a synchronization method, all other threads in the same instance that attempt to call the method (or other synchronization method) must wait. In order to exit the management process and give up control over the object to other waiting threads, threads with management processes only need to return from the synchronization method.
To understand the necessity of synchronization, let's start with a simple example where synchronization should be used but is not useful. The following program has three simple classes. First is Callme, which has a simple method to call( ). The call( ) method has a String parameter named msg. This method attempts to print an msg string in square brackets. The interesting thing is that after calling call( ) prints the left bracket and msg string, Thread.sleep(1000) is called, which pauses the current thread for 1 second.
The constructor of the next class Caller refers to an instance of Callme and a String, which are present in target and msg respectively. The constructor also creates a new thread that calls the run( ) method of the object. The thread starts immediately. The run( ) method of the Caller class calls the call( ) method of the Callme instance target through the msg string. Finally, the Synch class starts with creating a simple instance of Callme and three instances of Caller with different message strings.
The same instance of Callme is passed to each Caller instance.
// This program is not synchronized.class Callme { void call(String msg) { System.out.print("[" + msg); try { Thread.sleep(1000); } catch(Interru ptedException e) { System.out .println("Interrupted"); } System.out.println("]"); }}class Caller implements Runnable { String msg; Callme target; Thread t; public Caller(Ca llme targ, String s) { target = targ; msg = s; t = new Thread(this); t.start(); } public void run() { target.call(msg); }}class Synch { public static void main(String args[]) { Cal lme target = new Callme(); Caller ob1 = new Caller(target, "Hello"); Caller ob2 = new Caller(target, "Synchronized"); Caller ob3 = new Caller(target, "World"); // wait for threads to end try { ob1.t.join(); ob2.t.join(); ob3.t.join(); } catch(InterruptedException e) { System.out.println("Interrupted"); } }}
The output of this program is as follows:
Hello[Synchronized[World]]]
In this example, by calling sleep(), the call( ) method allows execution to be performed to another thread. This result is a mixed output of three message strings. In this program, there is no method that prevents three threads from calling the same method of the same object at the same time. This is a competition because three threads compete to complete the method. The example uses sleep( ) to make the effect repeat and obvious. In most cases, competition is more complex and unpredictable because you cannot be sure when context transformation will occur. This causes the program to run normally and sometimes error.
To achieve the purpose desired in the above example, you must have the right to use call( ). That is, at some point, it must be limited to only one thread that can control it. To do this, you just need to prepend the call( ) definition synchronized, as follows:
class Callme { synchronized void call(String msg) { ...
This prevents other threads from entering call( ) when one thread uses call( ). After synchronized is added to call( ), the program outputs as follows:
[Hello][Synchronized][World]
Any time in multithreading situations, if you have one method or multiple methods to manipulate the internal state of the object, you must use the synchronized keyword to prevent state competition. Remember that once a thread enters the synchronization method of the instance, no other thread can enter the synchronization method of the same instance. However, other async methods of this instance can still be called.
Synchronous statements
Although creating a synchronization method inside a created class is a simple and effective way to get synchronization, it is not effective at all times. Please think about the reasons behind this. Suppose you want to obtain synchronized access to class objects that are not designed for multithreaded access, that is, the class does not use the synchronized method. Moreover, the class is not created by you, but by a third party, and you cannot get its source code. In this way, you cannot prepend the synchronized modifier on the relevant method. How can an object of this class be synchronized? Fortunately, the solution is simple: you just need to put the call to the method defined by this class into a synchronized block.
Here is the normal form of synchronized statement:
synchronized(object) { // statements to be synchronized}
Where, object is a reference to the synchronized object. If all you want to synchronize is just a statement, then there is no need for curly braces. A synchronization block ensures that the call to the object member method only occurs after the current thread successfully enters the object pipeline.
The following is a modified version of the previous program, and the synchronization block is used in the run( ) method:
// This program uses a synchronized block.class Callme { void call(String msg) { System.out.print("[" + msg); try { Thread.sleep(1000); } catch ( InterruptedException e) { System. out.println("Interrupted"); } System.out.println("]"); }}class Caller implements Runnable { String msg; Callme target; Thread t; public Caller (Callme targ, String s) { target = targ ; msg = s; t = new Thread(this); t.start(); } // synchronize calls to call() public void run() { synchronized(target) { // synchronized block target t.call(msg); } }}class Synch1 { public static void main(String args[]) { Callme target = new Callme(); Caller ob1 = new Caller(target, "Hello"); Caller ob2 = ne w Caller(target, "Synchronized") ; Caller ob3 = new Caller(target, "World"); // wait for threads to end try { ob1.t.join(); ob2.t.join(); ob3.t.join(); } catch( InterruptedException e) { System.out.println("Interrupted"); } }}
Here, the call( ) method is not modified by synchronized. Synchronized is declared in the run() method of the Caller class. This gives the same correct result in the example above, because each thread waits for the end of the previous thread before it runs.
Java Inter-thread Communication <br />Multithreading replaces event loop programs by dividing tasks into discrete and logical units. Threads have a second advantage: it stays away from polling. Polling is usually achieved by a loop that repeats monitoring conditions. Once the conditions are established, appropriate actions must be taken. This wastes CPU time. For example, consider the classic sequence problem when one thread is generating data while another program is consuming it. To make the problem more interesting, assume that the data generator must wait for the consumer to finish the work before it can generate new data. In a polling system, consumers waste a lot of CPU cycles while waiting for the producer to generate data. Once the producer completes the work, it will start polling, wasting more CPU time waiting for the consumer's work to end, so on. Obviously, this situation is not popular.
To avoid polling, Java includes an inter-process communication mechanism implemented through wait( ), notify( ) and notifyAll( ) methods. These methods are implemented in objects with final methods, so all classes contain them. These three methods can only be called in the synchronized method. Although these methods are highly advanced in concept from the perspective of computer science vision, they are very simple to use in practice:
wait( ) tells the called thread to give up the pipe and go to sleep until other threads enter the same pipe and call notify( ).
notify( ) restores the first thread in the same object to call wait( ).
notifyAll( ) restores all threads in the same object that call wait( ). The thread with the highest priority runs first.
These methods are declared in the Object as follows:
final void wait( ) throws InterruptedException final void notify( ) final void notifyAll( )
Another form of wait( ) exists allows you to define the waiting time.
The following example program errors implement a simple producer/consumer problem. It consists of four classes: Q, which manages to get a synchronized sequence; Producer, which generates queued thread objects; Consumer, which consumes sequences; and PC, which creates a single Q, Producer, and Consumer subclasses.
// An incorrect implementation of a producer and consumer.class Q { int n; synchronized int get() { System.out.println("Got: " + n); return n; } synchroni zed void put(int n) { this .n = n; System.out.println("Put: " + n); }}class Producer implements Runnable { Q q; Producer(Q q) { this.q = q; new Thread(this, "Producer") .start(); } public void run() { int i = 0; while(true) { q.put(i++); } }}class Consumer implements Runnable { Q q; Consumer(Q q) { this.q = q; new Thread(this, "Consumer").start(); } public void run() { while(true) { q.get(); } }}class PC { public static void main(String args[]) { Q q = new Q(); new Producer(q); new Consumer(q); System.out.println("Press Control-C to stop."); }}
Although the put( ) and get( ) methods in the Q class are synchronized, nothing prevents the producer from surpassing the consumer, and nothing prevents the consumer from consuming the same sequence twice. This way you get the following error output (the output will change with processor speed and loaded tasks):
Put: 1Got: 1Got: 1Got: 1Got: 1Got: 1Put: 2Put: 3Put: 4Put: 5Put: 6Put: 7Got: 7
After the producer generates 1, the consumer obtains the same 15 times in turn. Producers continue to generate 2 to 7, and consumers have no chance to obtain them.
To correctly write this program in Java, use wait( ) and notify( ) to mark both directions, as shown below:
// A correct implementation of a producer and consumer.class Q { int n; boolean valueSet = false; synchronized int get() { if(!valueSet) try { wait(); } c atch(InterruptedException e) { System.out. println("InterruptedException caught"); } System.out.println("Got: " + n); valueSet = false; notify(); return n; } synchronized void put( int n) { if(valueSet) try { wait (); } catch(InterruptedException e) { System.out.println("InterruptedException caught"); } this.n = n; valueSet = true; System.out.println ("Put: " + n); notify() ; } } class Producer implements Runnable { Q q; Producer(Q q) { this.q = q; new Thread(this, "Producer").start(); } public void run() { int i = 0; While (true) { q.put(i++); } }}class Consumer implements Runnable { Q q; Consumer(Q q) { this.q = q; new Thread(this, "Consumer").start(); } public void run() { while(true) { q.get(); } }}class PCFixed { public static void main(String args[]) { Q q = new Q(); new Producer(q); new Consumer( q); System.out.println("Press Control-C to stop."); }}
The internal get( ), wait( ) is called. This hangs the execution until the Producer informs the data that it is ready. At this time, the internal get( ) is resumed. After getting the data, get( ) calls notify( ). This tells the Producer that you can enter more data into the sequence. In put(), wait() suspends execution until Consumer takes away the item in the sequence. When execution continues, the next data item is put into the sequence and notify( ) is called, which notify Consumer it should remove the data.
Here is the output of the program, which clearly shows the synchronization behavior:
Put: 1Got: 1Put: 2Got: 2Put: 3Got: 3Put: 4Got: 4Put: 5Got: 5