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(); }