Price programmer: How many of the five must-know methods for Java asynchronous invocation to synchronize?

Keywords: Programming Java

Let's start with the understanding of asynchronization and synchronization:

  • Synchronous call: The caller waits continuously for the result to be returned during the call.
  • Asynchronous call: During the call, the caller does not directly wait for the result to be returned, but performs other tasks, usually in the form of a callback function.

In fact, the difference between the two is still obvious. Let's not go into details here. The main point is how Java converts asynchronous calls into synchronization.In other words, you need to block continuously until you get the call result during an asynchronous call.

Without selling a pass, first list five methods, then give one example:

  1. Use wait and notify methods
  2. Use conditional locks
  3. Future
  4. Use CountDownLatch
  5. Using CyclicBarrier

0. Construct an asynchronous call

First, writing demo requires writing the infrastructure first, in which case you need to construct an asynchronous call model.Asynchronous call class:

public class AsyncCall {

    private Random random = new Random(System.currentTimeMillis());

    private ExecutorService tp = Executors.newSingleThreadExecutor();

    //demo1,2,4,5 Call Method
    public void call(BaseDemo demo){

        new Thread(()->{
            long res = random.nextInt(10);

            try {
                Thread.sleep(res*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            demo.callback(res);
        }).start();

    }

    //demo3 call method
    public Future<Long> futureCall(){

        return tp.submit(()-> {
            long res = random.nextInt(10);

            try {
                Thread.sleep(res*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return res;
        });

    }

    public void shutdown(){

        tp.shutdown();

    }

}

We are mainly concerned with the call method, which receives a demo parameter, opens a thread, performs specific tasks in the thread, and calls back functions using demo's callback method.You have noticed that the result returned here is a long integer of [0,10], and the number of results is how long the thread sleep s - mainly to better observe the experimental results and simulate the processing time during asynchronous calls.
The futureCall and shutdown methods, as well as the thread pool tp, are all intended for demo3 to implement with Future.
Base class for demo:

public abstract class BaseDemo {

    protected AsyncCall asyncCall = new AsyncCall();

    public abstract void callback(long response);

    public void call(){
        System.out.println("Initiate call");
        asyncCall.call(this);
        System.out.println("Call Return");
    }

}

BaseDemo is very simple. It contains an instance of an asynchronous call class, a call method for making asynchronous calls, and of course an abstract method, callback, which needs to be implemented by each demo - mainly through the corresponding processing in callbacks to achieve the purpose of asynchronous callback to synchronization.

1. Use wait and notify methods

This method actually uses the lock mechanism to paste code directly:

public class Demo1 extends BaseDemo{

    private final Object lock = new Object();

    @Override
    public void callback(long response) {
        System.out.println("Get results");
        System.out.println(response);
        System.out.println("End of Call");

        synchronized (lock) {
            lock.notifyAll();
        }

    }

    public static void main(String[] args) {

        Demo1 demo1 = new Demo1();

        demo1.call();

        synchronized (demo1.lock){
            try {
                demo1.lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Main Thread Content");

    }
}

You can see that after the call is made, the main thread uses wait to block and wait for the notify or notifyAll method to be called in the callback to wake up.Note that, as you know, both wait and notify need to acquire the lock on the object first.At the end of the main thread we print something, which is also used to verify the results of the experiment. Without wait and notify, the main thread content prints immediately after the call; like our code above, the main thread content waits until the callback function call ends before printing.
Print results without synchronization:

Initiate call
 Call Return
 Main Thread Content
 Get results
1
 End of Call

When synchronization is used:

Initiate call
 Call Return
 Get results
9
 End of Call
 Main Thread Content

2. Use conditional locks

Similar to method one:

public class Demo2 extends BaseDemo {

    private final Lock lock = new ReentrantLock();
    private final Condition con = lock.newCondition();

    @Override
    public void callback(long response) {

        System.out.println("Get results");
        System.out.println(response);
        System.out.println("End of Call");
        lock.lock();
        try {
            con.signal();
        }finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {

        Demo2 demo2 = new Demo2();

        demo2.call();

        demo2.lock.lock();

        try {
            demo2.con.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            demo2.lock.unlock();
        }
        System.out.println("Main Thread Content");
    }
}

Basically, there is no difference from the method except that conditional locks are used here, and their locking mechanisms are different.

3. Future

The Future method is not the same as before, and the asynchronous method we invoke is not the same.

public class Demo3{

    private AsyncCall asyncCall = new AsyncCall();

    public Future<Long> call(){

        Future<Long> future = asyncCall.futureCall();

        asyncCall.shutdown();

        return future;

    }

    public static void main(String[] args) {

        Demo3 demo3 = new Demo3();

        System.out.println("Initiate call");
        Future<Long> future = demo3.call();
        System.out.println("Return results");

        while (!future.isDone() && !future.isCancelled());

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("Main Thread Content");

    }
}

We call the futureCall method, which wants the thread pool tp to submit a Callable and then return a Future, which is what we got in the call in demo3. Once we get the future object, we can close the thread pool and call the shutdown method of asyncCall.One thing to note about closing thread pools is that we'll look back at the shutdown method of asyncCall:

public void shutdown(){

        tp.shutdown();

    }

We found that we simply invoked the shutdown method of the thread pool, and then we said the point to note that it is best not to use the shutdownNow method of tp here, which tries to interrupt the task being executed in the thread; that is, if we use this method, it is possible that our future__83
We then focus on what's in the main thread, where blocking is done by ourselves, and use future's isDone and isCancelled to determine the state of execution until execution is complete or canceled.We then print the result to get.

4. Use CountDownLatch

Using CountDownLatch is probably the most common and relatively elegant way to program everyday:

public class Demo4 extends BaseDemo{

    private final CountDownLatch countDownLatch = new CountDownLatch(1);

    @Override
    public void callback(long response) {

        System.out.println("Get results");
        System.out.println(response);
        System.out.println("End of Call");

        countDownLatch.countDown();

    }

    public static void main(String[] args) {

        Demo4 demo4 = new Demo4();

        demo4.call();

        try {
            demo4.countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main Thread Content");

    }
}

As you would normally use, here the await method of CountDownLatch is used to block in the main thread, and the countDown method is used in callbacks to keep the await part of the other threads running.
Of course, as in demo1 and demo2, blocked portions of the main thread can have a timeout set, after which they can no longer be blocked.

5. Use CyclicBarrier

public class Demo5 extends BaseDemo{

    private CyclicBarrier cyclicBarrier = new CyclicBarrier(2);

    @Override
    public void callback(long response) {

        System.out.println("Get results");
        System.out.println(response);
        System.out.println("End of Call");

        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {

        Demo5 demo5 = new Demo5();

        demo5.call();

        try {
            demo5.cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }

        System.out.println("Main Thread Content");

    }
}

Notice that CyclicBarrier and CountDownLatch are just similar, but they are different.For example, one can be understood as adding until the number is added to run together; the other is subtracting and reducing to zero to continue running.One is repeatable; the other is not, etc.
In addition, there are two things to note when using CyclicBarrier.First, at initialization, the number of parameters should be set to 2, because the asynchronous call here is a thread, and the main thread is a thread, which can only continue when both threads are await, which is the difference from CountDownLatch.Second, it is also about the initialization parameter value, which has nothing to do with demo here. You need to be careful when programming normally. If this value is set very large, which is larger than the number of threads in the thread pool, then deadlocks can easily be caused.

summary

To sum up, there are several ways to say this time.In fact, all methods work on the same principle, that is, blocking in the calling thread for the result and unblocking in the callback function.

Write at the end:

Welcome to my public number, there are a lot of Java related articles, learning materials will be updated and sorted data will be put in it.

If you think the writing is good, give a compliment and pay attention to it!Pay attention, don't get lost, keep updating!!!

Posted by algy on Sun, 21 Jun 2020 14:45:01 -0700