Detailed use of multithreaded synchronization based on Java review

  • 2020-04-01 01:53:44
  • OfStack

Firstly what is synchronous, asynchronous have what problem, and then discuss what measures can be taken to control the synchronization, then we would like to review the network communication, build a server-side "thread pool", the JDK provides us with a lot of concurrent toolkit, finally, we will explore the content of the inside.

Why thread synchronization?

When it comes to thread synchronization, for the most part, we're talking about" Single object multithreading "Is generally divided into two parts, one on" Shared variables "and one on" execution steps."

Shared variables

When we define a global variable in a thread object (Runnable) that is modified by the run method, the global variable's value will be modified by more than one thread at a time, resulting in an error. Let's look at the following code:


 Shared variables cause synchronization problems 
 class MyRunner implements Runnable
 {
     public int sum = 0;

     public void run() 
     {
         System.out.println(Thread.currentThread().getName() + " Start.");
         for (int i = 1; i <= 100; i++)
         {
             sum += i;
         }
         try {
             Thread.sleep(500);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + sum);
         System.out.println(Thread.currentThread().getName() + " End.");
     }
 }

 
 private static void sharedVaribleTest() throws InterruptedException
 {
     MyRunner runner = new MyRunner();
     Thread thread1 = new Thread(runner);
     Thread thread2 = new Thread(runner);
     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }

In this example, the thread is used to calculate what the sum from 1 to 100 is, and we know that the correct result is 5050. , but the result returned by the program above is 10100 because both threads operate on sum at the same time.

step

When we are running in multiple threads, we may need some operations to be combined as "atomic operations", that is, operations that can be thought of as "single threaded". For example, we may want the output to look like this:


 thread 1 Steps: 1
  thread 1 Steps: 2
  thread 1 Steps: 3
  thread 2 Steps: 1
  thread 2 Steps: 2
  thread 2 Steps: 3

If the synchronization is not well controlled, it might look like this:

 thread 1 Steps: 1
 thread 2 Steps: 1
 thread 1 Steps: 2
 thread 2 Steps: 2
 thread 1 Steps: 3
 thread 2 Steps: 3

Here we also give a sample code:

 Synchronization problems caused by chaotic execution steps 
 class MyNonSyncRunner implements Runnable
 {
     public void run() {
         System.out.println(Thread.currentThread().getName() + " Start.");
         for(int i = 1; i <= 5; i++)
         {
             System.out.println(Thread.currentThread().getName() + " Running step " + i);
             try
             {
                 Thread.sleep(50);
             }
             catch(InterruptedException ex)
             {
                 ex.printStackTrace();
             }
         }
         System.out.println(Thread.currentThread().getName() + " End.");
     }
 }

 
 private static void syncTest() throws InterruptedException
 {
     MyNonSyncRunner runner = new MyNonSyncRunner();
     Thread thread1 = new Thread(runner);
     Thread thread2 = new Thread(runner);
     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }

How to control thread synchronization

Since thread synchronization has the above problem, how should we solve it? We can adopt different strategies for synchronization problems caused by different reasons.

Control Shared variables

There are three ways to control Shared variables.

Change "single-object multithreading" to "multi-object multithreading"

As mentioned above, the synchronization problem usually occurs in the scenario of "single object multithreading", so the simplest way to deal with it is to modify the running model to look like "multiple object multithreading". For the synchronization problem in the above example, the modified code is as follows:


 Solution 1 to solve the problem of Shared variables 
 private static void sharedVaribleTest2() throws InterruptedException
 {
     Thread thread1 = new Thread(new MyRunner());
     Thread thread2 = new Thread(new MyRunner());
     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }

As you can see, the two threads in the code above use two different instances of Runnable, and they do not access the same global variable while running.
Demoting "global variables" to "local variables"

Since the problem is caused by Shared variables, we can change the Shared variable to "not Shared" and change it to a local variable. This can also solve the problem. Again, for the example above, the code for this solution is as follows:


 Solution 2 to solve the problem of Shared variables 
 class MyRunner2 implements Runnable
 {
     public void run() 
     {
         System.out.println(Thread.currentThread().getName() + " Start.");
         int sum = 0;
         for (int i = 1; i <= 100; i++)
         {
             sum += i;
         }
         try {
             Thread.sleep(500);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + sum);
         System.out.println(Thread.currentThread().getName() + " End.");
     }
 }

 
 private static void sharedVaribleTest3() throws InterruptedException
 {
     MyRunner2 runner = new MyRunner2();
     Thread thread1 = new Thread(runner);
     Thread thread2 = new Thread(runner);
     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }

As you can see, the sum variable has changed from a global variable to a local variable inside the run method.
Use the ThreadLocal mechanism

ThreadLocal is a mechanism introduced by the JDK to resolve the sharing of variables between threads. Variables declared using ThreadLocal are independent for each thread, even if they are global variables within the thread.

We can modify the above code in this way, as shown below:


 Solution 3 to solve the problem of Shared variables 
 class MyRunner3 implements Runnable
 {
     public ThreadLocal<Integer> tl = new ThreadLocal<Integer>();

     public void run() 
     {
         System.out.println(Thread.currentThread().getName() + " Start.");
         for (int i = 0; i <= 100; i++)
         {
             if (tl.get() == null)
             {
                 tl.set(new Integer(0));
             }
             int sum = ((Integer)tl.get()).intValue();
             sum+= i;
             tl.set(new Integer(sum));
             try {
                 Thread.sleep(10);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }

         System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + ((Integer)tl.get()).intValue());
         System.out.println(Thread.currentThread().getName() + " End.");
     }
 }

 
 private static void sharedVaribleTest4() throws InterruptedException
 {
     MyRunner3 runner = new MyRunner3();
     Thread thread1 = new Thread(runner);
     Thread thread2 = new Thread(runner);
     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }

To sum up, the first solution will reduce the efficiency of multi-threaded execution, so we recommend the second or third solution.

Control execution steps

When it comes to execution steps, we can solve them using the synchronized keyword.


 Execute the step problem solution 
 class MySyncRunner implements Runnable
 {
     public void run() {
         synchronized(this)
         {
             System.out.println(Thread.currentThread().getName() + " Start.");
             for(int i = 1; i <= 5; i++)
             {
                 System.out.println(Thread.currentThread().getName() + " Running step " + i);
                 try
                 {
                     Thread.sleep(50);
                 }
                 catch(InterruptedException ex)
                 {
                     ex.printStackTrace();
                 }
             }
             System.out.println(Thread.currentThread().getName() + " End.");
         }
     }
 }

 
 private static void syncTest2() throws InterruptedException
 {
     MySyncRunner runner = new MySyncRunner();
     Thread thread1 = new Thread(runner);
     Thread thread2 = new Thread(runner);
     thread1.setDaemon(true);
     thread2.setDaemon(true);
     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }

Synchronized is a very important keyword on the topic of thread synchronization. Its principle is similar to that of transaction locking in a database. In use, we should try to reduce the coverage of synchronized for two reasons: 1) the coverage of synchronized is serial and inefficient; 2) prone to deadlock. Let's look at the following example:

synchronized The sample 
 private static void syncTest3() throws InterruptedException
 {
     final List<Integer> list = new ArrayList<Integer>();

     Thread thread1 = new Thread()
     {
         public void run()
         {
             System.out.println(Thread.currentThread().getName() + " Start.");
             Random r = new Random(100);
             synchronized(list)
             {
                 for (int i = 0; i < 5; i++)
                 {
                     list.add(new Integer(r.nextInt()));
                 }
                 System.out.println("The size of list is " + list.size());
             }
             try
             {
                 Thread.sleep(500);
             }
             catch(InterruptedException ex)
             {
                 ex.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName() + " End.");
         }
     };

     Thread thread2 = new Thread()
     {
         public void run()
         {
             System.out.println(Thread.currentThread().getName() + " Start.");
             Random r = new Random(100);
             synchronized(list)
             {
                 for (int i = 0; i < 5; i++)
                 {
                     list.add(new Integer(r.nextInt()));
                 }
                 System.out.println("The size of list is " + list.size());
             }
             try
             {
                 Thread.sleep(500);
             }
             catch(InterruptedException ex)
             {
                 ex.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName() + " End.");
         }
     };

     thread1.start();
     thread2.start();
     thread1.join();
     thread2.join();
 }

We should concentrate on what needs to be synchronized and try not to include other unrelated, resource-intensive operations, and the thread-sleep operations in the example should clearly not be included.

Construct thread pool

We are < (link: #) > , we have built a Socket connection pool, on this basis, we build a thread pool, complete the basic start, sleep, wake, stop operations.

The basic idea is to maintain a series of threads in the form of an array, through Socket communication, the client side to send commands to the server side, when the server side received the command, according to the received command in the thread array to operate.

Socket client code remains the same, still using the Socket connection pool when the code, we mainly for the server side for transformation.

First, we need to define a thread object that will be used to perform our business operations. For simplicity, let the thread sleep only.


 Define a thread object 
 enum ThreadStatus
 {
     Initial,
     Running,
     Sleeping,
     Stopped
 }

 enum ThreadTask
 {
     Start,
     Stop,
     Sleep,
     Wakeup
 }

 
 class MyThread extends Thread
 {
     public ThreadStatus status = ThreadStatus.Initial;
     public ThreadTask task;
     public void run()
     {
         status = ThreadStatus.Running;
         while(true)
         {
             try {
                 Thread.sleep(3000);
                 if (status == ThreadStatus.Sleeping)
                 {
                     System.out.println(Thread.currentThread().getName() + "  Go into hibernation. ");
                     this.wait();
                 }
             } catch (InterruptedException e) {
                 System.out.println(Thread.currentThread().getName() + "  An error occurred during the run. ");
                 status = ThreadStatus.Stopped;
             }
         }
     }
 }

Then, we need to define a thread manager to manage the threads in the thread pool. The code is as follows:

 Defines a thread pool management object 
 class MyThreadManager
 {
     public static void manageThread(MyThread[] threads, ThreadTask task)
     {
         for (int i = 0; i < threads.length; i++)
         {
             synchronized(threads[i])
             {
                 manageThread(threads[i], task);
             }
         }
         System.out.println(getThreadStatus(threads));
     }

     public static void manageThread(MyThread thread, ThreadTask task)
     {
         if (task == ThreadTask.Start)
         {
             if (thread.status == ThreadStatus.Running)
             {
                 return;
             }
             if (thread.status == ThreadStatus.Stopped)
             {
                 thread = new MyThread();
             }
             thread.status = ThreadStatus.Running;
             thread.start();

         }
         else if (task == ThreadTask.Stop)
         {
             if (thread.status != ThreadStatus.Stopped)
             {
                 thread.interrupt();
                 thread.status = ThreadStatus.Stopped;
             }
         }
         else if (task == ThreadTask.Sleep)
         {
             thread.status = ThreadStatus.Sleeping;
         }
         else if (task == ThreadTask.Wakeup)
         {
             thread.notify();
             thread.status = ThreadStatus.Running;
         }
     }

     public static String getThreadStatus(MyThread[] threads)
     {
         StringBuffer sb = new StringBuffer();
         for (int i = 0; i < threads.length; i++)
         {
             sb.append(threads[i].getName() + " The status of: " + threads[i].status).append("rn");
         }
         return sb.toString();
     }
 }

Finally, there is our server side, which continues to accept requests from clients. Every time we receive a connection request, a new thread is opened on the server side to handle various operation instructions from subsequent clients.

 Defines a server-side thread pool object 
 public class MyThreadPool {

     public static void main(String[] args) throws IOException
     {
         MyThreadPool pool = new MyThreadPool(5);
     }

     private int threadCount;
     private MyThread[] threads = null;

     
     public MyThreadPool(int count) throws IOException
     {
         this.threadCount = count;
         threads = new MyThread[count];
         for (int i = 0; i < threads.length; i++)
         {
             threads[i] = new MyThread();
             threads[i].start();
         }
         Init();
     }

     private void Init() throws IOException
     {
         ServerSocket serverSocket = new ServerSocket(5678);
         while(true)
         {
             final Socket socket = serverSocket.accept();
             Thread thread = new Thread()
             {
                 public void run()
                 {
                     try
                     {
                         System.out.println(" A new one has been detected Socket The connection. ");
                         BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                         PrintStream ps = new PrintStream(socket.getOutputStream());
                         String line = null;
                         while((line = br.readLine()) != null)
                         {
                             System.out.println(line);
                             if (line.equals("Count"))
                             {
                                 System.out.println(" In the thread pool 5 A thread ");
                             }
                             else if (line.equals("Status"))
                             {
                                 String status = MyThreadManager.getThreadStatus(threads);
                                 System.out.println(status);
                             }
                             else if (line.equals("StartAll"))
                             {
                                 MyThreadManager.manageThread(threads, ThreadTask.Start);
                             }
                             else if (line.equals("StopAll"))
                             {
                                 MyThreadManager.manageThread(threads, ThreadTask.Stop);
                             }
                             else if (line.equals("SleepAll"))
                             {
                                 MyThreadManager.manageThread(threads, ThreadTask.Sleep);
                             }
                             else if (line.equals("WakeupAll"))
                             {
                                 MyThreadManager.manageThread(threads, ThreadTask.Wakeup);
                             }
                             else if (line.equals("End"))
                             {
                                 break;
                             }
                             else
                             {
                                 System.out.println("Command:" + line);
                             }
                             ps.println("OK");
                             ps.flush();
                         }
                     }
                     catch(Exception ex)
                     {
                         ex.printStackTrace();
                     }
                 }
             };
             thread.start();
         }
     }
 }

Explore the concurrent toolkit in the JDK

To simplify the workload of developers doing multithreaded development and reduce bugs in their programs, the JDK provides a concurrent toolkit that allows us to easily develop multithreaded programs.
The thread pool

We implemented a very "crude" thread pool above, which is also provided in the concurrent toolkit and is very easy to use.

The thread pools in the concurrent toolkit are divided into three categories: ScheduledThreadPool, FixedThreadPool, and CachedThreadPool.

First let's define a Runnable object


 define Runnable object 
 class MyRunner implements Runnable
 {
     public void run() {
         System.out.println(Thread.currentThread().getName() + " Running start ");
         for(int i = 0; i < 1; i++)
         {
             try
             {
                 System.out.println(Thread.currentThread().getName() + " The running ");
                 Thread.sleep(200);
             }
             catch(Exception ex)
             {
                 ex.printStackTrace();
             }
         }
         System.out.println(Thread.currentThread().getName() + " End of the run ");
     }
 }

As you can see, its function is very simple, just output thread execution process.

ScheduledThreadPool

This is similar to the usual ScheduledTask we use, or much like a Timer, which causes a thread to start running for a specified period of time and to run again at a different interval until the thread pool is closed.

The sample code is as follows:


ScheduledThreadPool The sample 
 private static void scheduledThreadPoolTest()
 {
     final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);

     MyRunner runner = new MyRunner();

     final ScheduledFuture<?> handler1 = scheduler.scheduleAtFixedRate(runner, 1, 10, TimeUnit.SECONDS);
     final ScheduledFuture<?> handler2 = scheduler.scheduleWithFixedDelay(runner, 2, 10, TimeUnit.SECONDS);

     scheduler.schedule(new Runnable()
     {
         public void run()
         {
             handler1.cancel(true);
             handler2.cancel(true);
             scheduler.shutdown();
         }
     }, 30, TimeUnit.SECONDS
     );
 }

FixedThreadPool

This is a pool of threads with a specified capacity, which means that we can specify that at most more than one thread is running in the pool at the same time.

Look at the following code:


FixedThreadPool The sample 
 private static void fixedThreadPoolTest()
 {
     ExecutorService exec = Executors.newFixedThreadPool(3);
     for(int i = 0; i < 5; i++)
     {
         MyRunner runner = new MyRunner();
         exec.execute(runner);
     }
     exec.shutdown();
 }

Note its output:

pool-1-thread-1 Running start 
pool-1-thread-1 The running 
pool-1-thread-2 Running start 
pool-1-thread-2 The running 
pool-1-thread-3 Running start 
pool-1-thread-3 The running 
pool-1-thread-1 End of the run 
pool-1-thread-1 Running start 
pool-1-thread-1 The running 
pool-1-thread-2 End of the run 
pool-1-thread-2 Running start 
pool-1-thread-2 The running 
pool-1-thread-3 End of the run 
pool-1-thread-1 End of the run 
pool-1-thread-2 End of the run 

You can see that from start to finish, up to three threads are running at the same time.
CachedThreadPool

This is another thread pool that does not need to specify a capacity and creates new threads whenever it is needed.

It works very much like a FixedThreadPool, so here's the code:


CachedThreadPool The sample 
 private static void cachedThreadPoolTest()
 {
     ExecutorService exec = Executors.newCachedThreadPool();
     for(int i = 0; i < 5; i++)
     {
         MyRunner runner = new MyRunner();
         exec.execute(runner);
     }
     exec.shutdown();
 }

Its execution results are as follows:

pool-1-thread-1 Running start 
pool-1-thread-1 The running 
pool-1-thread-2 Running start 
pool-1-thread-2 The running 
pool-1-thread-3 Running start 
pool-1-thread-3 The running 
pool-1-thread-4 Running start 
pool-1-thread-4 The running 
pool-1-thread-5 Running start 
pool-1-thread-5 The running 
pool-1-thread-1 End of the run 
pool-1-thread-2 End of the run 
pool-1-thread-3 End of the run 
pool-1-thread-4 End of the run 
pool-1-thread-5 End of the run 

As you can see, it creates five threads.
The processing thread returns a value

In some cases, we need to use the return value of the thread, and in all of the code above, the thread is performing some operation without any return value.

How do you do that? We can use Callable< in JDK; T> And CompletionService< T> , the former returns the results of a single thread, the latter returns the results of a group of threads.
Returns the result of a single thread

Just look at the code:


Callable The sample 
 private static void callableTest() throws InterruptedException, ExecutionException
 {
     ExecutorService exec = Executors.newFixedThreadPool(1);
     Callable<String> call = new Callable<String>()
     {
         public String call()
         {
             return "Hello World.";
         }
     };
     Future<String> result = exec.submit(call);
     System.out.println(" The return value of the thread is " + result.get());
     exec.shutdown();
 }

The implementation results are as follows:

 The return value of the thread is Hello World.

Returns the result of each thread in the thread pool

I need to use CompletionService< T> , the code is as follows:


CompletionService The sample 
 private static void completionServiceTest() throws InterruptedException, ExecutionException
 {
     ExecutorService exec = Executors.newFixedThreadPool(10);
     CompletionService<String> service = new ExecutorCompletionService<String>(exec);
     for (int i = 0; i < 10; i++)
     {
         Callable<String> call = new Callable<String>()
         {
             public String call() throws InterruptedException
             {
                 return Thread.currentThread().getName();
             }
         };
         service.submit(call);
     }

     Thread.sleep(1000);
     for(int i = 0; i < 10; i++)
     {
         Future<String> result = service.take();
         System.out.println(" The return value of the thread is " + result.get());
     }
     exec.shutdown();
 }

The implementation results are as follows:

 The return value of the thread is pool-2-thread-1
 The return value of the thread is pool-2-thread-2
 The return value of the thread is pool-2-thread-3
 The return value of the thread is pool-2-thread-5
 The return value of the thread is pool-2-thread-4
 The return value of the thread is pool-2-thread-6
 The return value of the thread is pool-2-thread-8
 The return value of the thread is pool-2-thread-7
 The return value of the thread is pool-2-thread-9
 The return value of the thread is pool-2-thread-10

Implement the producer-consumer model

We're all familiar with producer-consumer models, and we usually implement them using some kind of data structure. In the concurrent toolkit, we can implement the producer-consumer model using BlockingQueue, as follows:


BlockingQueue The sample 
 public class BlockingQueueSample {

     public static void main(String[] args)
     {
         blockingQueueTest();
     }

     private static void blockingQueueTest()
     {
         final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
         final int maxSleepTimeForSetter = 10;
         final int maxSleepTimerForGetter = 10;

         Runnable setter = new Runnable()
         {
             public void run()
             {
                 Random r = new Random();
                 while(true)
                 {
                     int value = r.nextInt(100);
                     try
                     {
                         queue.put(new Integer(value));
                         System.out.println(Thread.currentThread().getName() + "--- Insert a value into the queue " + value);
                         Thread.sleep(r.nextInt(maxSleepTimeForSetter) * 1000);
                     }
                     catch(Exception ex)
                     {
                         ex.printStackTrace();
                     }
                 }
             }
         };

         Runnable getter = new Runnable()
         {
             public void run()
             {
                 Random r = new Random();
                 while(true)
                 {
                     try
                     {
                         if (queue.size() == 0)
                         {
                             System.out.println(Thread.currentThread().getName() + "--- The queue is empty ");
                         }
                         else
                         {
                             int value = queue.take().intValue();
                             System.out.println(Thread.currentThread().getName() + "--- Get the value from the queue " + value);
                         }
                         Thread.sleep(r.nextInt(maxSleepTimerForGetter) * 1000);
                     }
                     catch(Exception ex)
                     {
                         ex.printStackTrace();
                     }
                 }
             }
         };

         ExecutorService exec = Executors.newFixedThreadPool(2);
         exec.execute(setter);
         exec.execute(getter);
     }
 }

We define two threads, one that adds data to the Queue and one that fetches data from it. We can make the program produce different results by controlling the values of the maxSleepTimeForSetter and maxSleepTimerForGetter.

The possible implementation results are as follows:


pool-1-thread-1--- Insert a value into the queue 88
pool-1-thread-2--- Get the value from the queue 88
pool-1-thread-1--- Insert a value into the queue 75
pool-1-thread-2--- Get the value from the queue 75
pool-1-thread-2--- The queue is empty 
pool-1-thread-2--- The queue is empty 
pool-1-thread-2--- The queue is empty 
pool-1-thread-1--- Insert a value into the queue 50
pool-1-thread-2--- Get the value from the queue 50
pool-1-thread-2--- The queue is empty 
pool-1-thread-2--- The queue is empty 
pool-1-thread-2--- The queue is empty 
pool-1-thread-2--- The queue is empty 
pool-1-thread-2--- The queue is empty 
pool-1-thread-1--- Insert a value into the queue 51
pool-1-thread-1--- Insert a value into the queue 92
pool-1-thread-2--- Get the value from the queue 51
pool-1-thread-2--- Get the value from the queue 92

Because the values in the Queue and the Thread's sleep times are both random, the execution results are not fixed.

Use semaphores to control threads

The JDK provides Semaphore to implement "semaphores". It provides two methods to acquire and release semaphores: acquire and release. The sample code is as follows:


SemaPhore The sample 
 private static void semaphoreTest()
 {
     ExecutorService exec = Executors.newFixedThreadPool(10);
     final Semaphore semp = new Semaphore(2);

     for (int i = 0; i < 10; i++)
     {
         Runnable runner = new Runnable()
         {
             public void run()
             {
                 try
                 {
                     semp.acquire();
                     System.out.println(new Date() + " " + Thread.currentThread().getName() + " In progress. ");
                     Thread.sleep(5000);
                     semp.release();
                 }
                 catch(Exception ex)
                 {
                     ex.printStackTrace();
                 }
             }
         };
         exec.execute(runner);
     }

     exec.shutdown();
 }

The implementation results are as follows:

Tue May 07 11:22:11 CST 2013 pool-1-thread-1 In progress. 
Tue May 07 11:22:11 CST 2013 pool-1-thread-2 In progress. 
Tue May 07 11:22:17 CST 2013 pool-1-thread-3 In progress. 
Tue May 07 11:22:17 CST 2013 pool-1-thread-4 In progress. 
Tue May 07 11:22:22 CST 2013 pool-1-thread-5 In progress. 
Tue May 07 11:22:22 CST 2013 pool-1-thread-6 In progress. 
Tue May 07 11:22:27 CST 2013 pool-1-thread-7 In progress. 
Tue May 07 11:22:27 CST 2013 pool-1-thread-8 In progress. 
Tue May 07 11:22:32 CST 2013 pool-1-thread-10 In progress. 
Tue May 07 11:22:32 CST 2013 pool-1-thread-9 In progress. 

As you can see, although 10 threads are created in the thread pool, only 2 threads are running at the same time.
Controls the execution steps of all threads in the thread pool

We've already mentioned that the synchronized keyword can be used to control the execution steps in a single thread, so how do we do that if we want to control the execution steps of all threads in the thread pool?

There are two ways to do this, CyclicBarrier and CountDownLatch.

CyclicBarrier USES a mechanism similar to object.wait. Its constructor needs to receive an integer number to indicate the number of threads it needs to control. When its await method is called in the thread's run method, it guarantees that all threads have executed this step before proceeding to the next step.

The sample code is as follows:


CyclicBarrier The sample 
 class MyRunner2 implements Runnable
 {
     priva
                

Related articles: