Java multithreading concurrency best practices

It is difficult to write concurrent code. Although the Java language provides many synchronization and concurrency support, it still needs personal diligence and professional knowledge to write Java Concurrent code without bugs. Java multithreading concurrency best practice is a set of good ideas to help you quickly develop high-quality concurrent code. If you are a novice, you need to be familiar with some basic concepts. Reading this article will be more targeted.

1. Use local variables

Local variables should always be used instead of creating a class or instance variable. Generally, developers can save memory and reuse by using object instances as variables, because they think that creating local variables in methods will consume a lot of memory each time. The execute () method of the following code is called by multiple threads. In order to realize a new function, you need a temporary Collection collection, which is used as a static class variable in the code, and then clear the Collection at the end of the execute method for reuse next time. The person who wrote this code may think it is thread safe, Because CopyOnWriteArrayList is thread safe, but he doesn't realize that this method execute() is called by multiple threads, then one thread in multiple threads may see the temporary data of another thread, even if collections Synchronizedlist also cannot guarantee the logical invariance in the execute () method. This invariance is that this Collection is a temporary Collection, which is only visible inside the execution of each thread and cannot be exposed to other threads.

The solution is to use a local List instead of a global List.

2. Use immutable classes

Immutable classes, such as String Integer, will not change once they are created. Immutable classes can reduce the amount of synchronization required in the code.

3. Minimize the scope of the lock

Any code in the lock cannot be executed concurrently. If you have 5% of the code in the lock, your application image cannot be improved more than 20 times according to Amdahl's law, because these codes in the lock can only be executed in sequence, reducing the scope of the lock. The less code between locking and unlocking, the better.

4. Use the Excutor of the thread pool instead of directly executing new Thread

Creating a thread is expensive. If you want to get a scalable Java application, you need to use thread pool to manage threads. JDK provides various ThreadPool thread pools and executors.

5. Prefer wet clothes synchronization to thread wait notify

Since Java 1.5, synchronization tools such as CycicBariier, CountDownLatch and Sempahore have been added. You should give priority to using these synchronization tools instead of thinking about how to use thread wait and notify. The design of production consumption through BlockingQueue is much better than using thread wait and notify. You can also use CountDownLatch to realize the waiting of multiple threads:

import java.util.Date;

import java.util.concurrent.CountDownLatch;

import java.util.logging.Level;

import java.util.logging.Logger;



/**

 * Java program to demonstrate How to use CountDownLatch in Java. CountDownLatch is

 * useful if you want to start main processing thread once its dependency is completed

 * as illustrated in this CountDownLatch Example

 *

 * @author Javin Paul

 */

public class CountDownLatchDemo {



    public static void main(String args[]) {

       final CountDownLatch latch = new CountDownLatch(3);

       Thread cacheService = new Thread(new Service("CacheService", 1000, latch));

       Thread alertService = new Thread(new Service("AlertService", 1000, latch));

       Thread validationService = new Thread(new Service("ValidationService", 1000, latch));



       cacheService.start(); //separate thread will initialize CacheService

       alertService.start(); //another thread for AlertService initialization

       validationService.start();



       // application should not start processing any thread until all service is up

       // and ready to do there job.

       // Countdown latch is idle choice here, main thread will start with count 3

       // and wait until count reaches zero. each thread once up and read will do

       // a count down. this will ensure that main thread is not started processing

       // until all services is up.



       //count is 3 since we have 3 Threads (Services)



       try{

            latch.await();  //main thread is waiting on CountDownLatch to finish

            System.out.println("All services are up, Application is starting now");

       }catch(InterruptedException ie){

           ie.printStackTrace();

       }



    }



}
/**

 * Service class which will be executed by Thread using CountDownLatch synchronizer.

 */

class Service implements Runnable{

    private final String name;

    private final int timeToStart;

    private final CountDownLatch latch;



    public Service(String name, int timeToStart, CountDownLatch latch){

        this.name = name;

        this.timeToStart = timeToStart;

        this.latch = latch;

    }



    @Override

    public void run() {

        try {

            Thread.sleep(timeToStart);

        } catch (InterruptedException ex) {

            Logger.getLogger(Service.class.getName()).log(Level.SEVERE, null, ex);

        }

        System.out.println( name + " is Up");

        latch.countDown(); //reduce count of CountDownLatch by 1

    }



}
Output:

ValidationService is Up

AlertService is Up

CacheService is Up

All services are up, Application is starting now

6. Use BlockingQueue to realize production consumption mode

Most concurrency problems can be implemented by producer consumer production consumption design, and BlockingQueue is the best implementation method. The blocked queue can handle not only a single production and a single consumption, but also multiple production and consumption. The following code:

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.LinkedBlockingQueue;

import java.util.logging.Level;

import java.util.logging.Logger;



public class ProducerConsumerPattern {



    public static void main(String args[]){



     //Creating shared object

     BlockingQueue sharedQueue = new LinkedBlockingQueue();



     //Creating Producer and Consumer Thread

     Thread prodThread = new Thread(new Producer(sharedQueue));

     Thread consThread = new Thread(new Consumer(sharedQueue));



     //Starting producer and Consumer thread

     prodThread.start();

     consThread.start();

    }



}
//Producer Class in java

class Producer implements Runnable {



    private final BlockingQueue sharedQueue;



    public Producer(BlockingQueue sharedQueue) {

        this.sharedQueue = sharedQueue;

    }



    @Override

    public void run() {

        for(int i=0; i<10; i++){

            try {

                System.out.println("Produced: " + i);

                sharedQueue.put(i);

            } catch (InterruptedException ex) {

                Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);

            }

        }

    }



}
//Consumer Class in Java

class Consumer implements Runnable{



    private final BlockingQueue sharedQueue;



    public Consumer (BlockingQueue sharedQueue) {

        this.sharedQueue = sharedQueue;

    }



    @Override

    public void run() {

        while(true){

            try {

                System.out.println("Consumed: "+ sharedQueue.take());

            } catch (InterruptedException ex) {

                Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);

            }

        }

    }





}
Output:

Produced: 0

Produced: 1

Consumed: 0

Produced: 2

Consumed: 1

Produced: 3

Consumed: 2

Produced: 4

Consumed: 3

Produced: 5

Consumed: 4

Produced: 6

Consumed: 5

Produced: 7

Consumed: 6

Produced: 8

Consumed: 7

Produced: 9

Consumed: 8

Consumed: 9

7. Use concurrent collections instead of collections with synchronization locks

Java provides five concurrent Collections: concurrent HashMap, CopyOnWriteArrayList, CopyOnWriteArraySet, BlockingQueue Deque and BlockingDeque. You'd rather use these collections than collections Syncronizedlist and other collections with synchronization locks. CopyOnWriteArrayList is suitable for situations where there are mainly reads and few writes. ConcurrentHashMap is a frequently used concurrent collection

8. Use Semaphore to create bounded

In order to establish a reliable and stable system, resources such as database file system and socket must be bound ed. Semaphore is an option to limit the cost of these resources. If a resource is not available, semaphore can block the thread waiting at the lowest cost:

import java.util.concurrent.Semaphore;

public class SemaphoreTest {

    Semaphore binary = new Semaphore(1);

    public static void main(String args[]) {
        final SemaphoreTest test = new SemaphoreTest();
        new Thread(){
            @Override
            public void run(){
              test.mutualExclusion(); 
            }
        }.start();

        new Thread(){
            @Override
            public void run(){
              test.mutualExclusion(); 
            }
        }.start();

    }

    private void mutualExclusion() {
        try {
            binary.acquire();

            //mutual exclusive region
            System.out.println(Thread.currentThread().getName() + " inside mutual exclusive region");
            Thread.sleep(1000);

        } catch (InterruptedException i.e.) {
            ie.printStackTrace();
        } finally {
            binary.release();
            System.out.println(Thread.currentThread().getName() + " outside of mutual exclusive region");
        }
    } 

}
Output:
Thread-0 inside mutual exclusive region
Thread-0 outside of mutual exclusive region
Thread-1 inside mutual exclusive region
Thread-1 outside of mutual exclusive region

9. It is better to use synchronous code block than synchronous method

Using synchronized synchronized code blocks will only lock one object, not the whole current method; If you change the fields of a common variable or class, first select the atomic variable, and then use volatile. If you need a mutex lock, consider using ReentrantLock

10. Avoid using static variables

Static variables will cause many problems in the concurrent execution environment. If you must use static variables, let them be called final constants. If they are used to save Collection, consider using read-only Collection.

11. Prefer locks to synchronized keywords

The Lock interface is very powerful and fine-grained. There are different locks for read and write operations, which can easily expand and expand. synchronized will not automatically release the Lock. If you Lock with lock(), you can unlock with unlock:

lock.lock();

try {

 //do something ...

} finally {

 lock.unlock();

 }

Added by Foregone96 on Wed, 09 Mar 2022 14:45:14 +0200