My story with Java Thread

catalogue

Inconsistent with the envisaged results 1

guess

verification

conclusion

Inconsistent with the envisaged results 2

  analysis

The main thread and other sub threads are executed before execution

Thread.join method

CountDownLatch

CyclicBarrier

reference material

Let's start with a simple code:

public static void main(String[] args) {
	Thread thread = new Thread(new Runnable() {
		@Override
		public void run() {
			for(int i = 1; i < 6; i++) {
				System.out.println(i);
			}
		}
	});

	thread.start();
	System.out.println("Main thread end.");
}

What will be the result of this code? The results are as follows:

  I ran it 10 times, and it is still the result shown in the figure above.

Inconsistent with the envisaged results 1

According to the previous assumption, the thread and the main thread are executed in parallel or concurrently. In the print result, "Main thread end." should appear anywhere in 1, 2, 3, 4 and 5. Why is it the same result when executed 10 times?

guess

Now threads are generally scheduled according to time slice. In the above test code, the amount of code is small. Whether it is the main thread or thread thread, it is enough to complete processing in one time slice. So, the result is the same in the end.

verification

So, let's change the code to make the main thread execute a little longer.

public static void main(String[] args) throws InterruptedException {
	Thread thread = new Thread(new Runnable() {
		@Override
		public void run() {
			for(int i = 1; i < 6; i++) {
				System.out.println(i);
			}
		}
	});

	thread.start();
	Thread.sleep(1);
	System.out.println("Main thread end.");
}

The printing position of the running result "Main thread end." is random, as shown in the following figure:

conclusion

The guess is correct. The execution time of the main thread and thread thread is too short. Both threads are completed in a time slice, so the execution results are always the same.

Inconsistent with the envisaged results 2

Before execution, another result was also envisaged: when the main thread ends, the sub thread ends. When the sub thread ends, it may be printed to any one of 1, 2, 3, 4 and 5. The envisaged results are as follows:

 

 

  analysis

After analysis, it is related to the daemon thread.

Daemon thread: a thread that serves other threads. The feature is that in the Java virtual machine, if only the daemon thread is left, the virtual machine will exit. Because the daemon thread serves other threads, if the threads of the service end, the existence of the daemon thread is meaningless, and there is no need for the virtual machine to continue running. For example, the GC thread of the virtual machine is a guard thread. If other threads end, there will be no garbage. The GC thread has no meaning to exist, and the virtual machine can exit.

It can be seen from the Thread source code that when creating a new Thread, the daemon properties of the Thread follow the parent Thread, as follows:

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    /** Omit some codes*/

    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    
    /** Omit some codes*/
}

The parent thread, that is, the main thread, is naturally not a daemon thread. So thread is not a daemon thread.

In order to realize the result in the conjecture, set the thread thread as the guard thread, and add some delay codes. The codes are as follows:

public static void main(String[] args) throws InterruptedException {
	Thread thread = new Thread(new Runnable() {
		@Override
		public void run() {
			for(int i = 1; i < 6; i++) {
				System.out.println(i);

				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	});

	thread.setDaemon(true);
	thread.start();
	System.out.println("Main thread end.");
	Thread.sleep(new Random().nextInt(500));
}

The main thread and other sub threads are executed before execution

If no processing is added, the main thread and sub thread are processed concurrently or in parallel, and there is no mandatory order. There are three common methods to implement the main thread and other sub threads before execution:

  1. Thread.join method;
  2. CountDownLatch;
  3. CyclicBarrier.

Thread.join method

The join method allows other threads to wait for the thread calling the join to complete before executing. For example, when thread.join is called in the main thread, the main thread will wait for the execution of thread to go down.

The essence of the join method is to treat the thread as a monitor, and wait and wake up through the monitor's wait method and notifyAll method. For example, the thread thread is used as a monitor, calling the thread.join method in the main thread, which is equivalent to calling the thread.wait method. When the thread runs, the thread.notifyAll method will be called. For in-depth analysis, refer to the following resources: join() principle of Java Thread.

The code is as follows:

public static void main(String[] args) throws InterruptedException {
	Thread thread = new Thread(new Runnable() {
		@Override
		public void run() {
			for(int i = 1; i < 6; i++) {
				System.out.println(i);

				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	});

	thread.start();
	thread.join();
	System.out.println("Main thread end.");
}

The operation results are as follows:

CountDownLatch

CountDownLatch is equivalent to a counter. There are three steps to use:

  1. Initialization, give the count size;
  2. After completing the task, the counter is decremented by one;
  3. Wait where needed;

The code of step 1 is as follows:

CountDownLatch latch = new CountDownLatch(1);

The code of step 2 is as follows:

latch.countDown();

The code of step 3 is as follows (note that it is the await method, not the wait method):

latch.await();

The complete code is as follows:

public static void main(String[] args) throws InterruptedException {
	CountDownLatch latch = new CountDownLatch(1);

	Thread thread = new Thread(new Runnable() {
		@Override
		public void run() {
			for(int i = 1; i < 6; i++) {
				System.out.println(i);

				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			latch.countDown();
		}
	});

	thread.start();
	latch.await();
	System.out.println("Main thread end.");
}

CyclicBarrier

CyclicBarrier is called a barrier, which is similar to CountDowanLatch. It also has three steps:

  1. Initialization, given the size of the barrier (to achieve the same effect, the size is 1 larger than CountDowanLatch);
  2. Notify the arrival barrier after completing the task;
  3. All tasks reach the barrier and proceed to the next step;

The code of step 1 is as follows:

CyclicBarrier barrier = new CyclicBarrier(2);

Step 2 Code:

barrier.await();

Step 3 code:

barrier.await();

The complete code is as follows:

public static void main(String[] args) throws Exception {
	CyclicBarrier barrier = new CyclicBarrier(2);

	Thread thread = new Thread(new Runnable() {
		@Override
		public void run() {
			for(int i = 1; i < 6; i++) {
				System.out.println(i);

				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			try {
				barrier.await();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	});

	thread.start();
	barrier.await();
	System.out.println("Main thread end.");
}

Of course, the detailed differences between CyclicBarrier and CountDownLatch will not be discussed here.

reference material

join() principle of Java Thread

 

 

Tags: Java thread

Posted on Sun, 05 Dec 2021 14:52:06 -0500 by shainh