In Java, the threading part is a big focus, and this article is all about threads. JUC stands for java.util.Concurrent toolkit. This is a toolkit for working with threads, which started with JDK 1.5. Let’s see how it works.

Welcome everyone to pay attention to my public 1314 source code, is slowly moving the article to the public number, after the nuggets and the public number article will be synchronized update, and the article is free in the public number.

Volatile keyword and memory visibility

1. Memory visibility: Let’s start with the following code:

Public class TestVolatile {public static void main(String[] args){// ThreadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.start(); While (true){if (threaddemo.isFlag ()){system.out.println (" threaddemo.isflag () + threaddemo.isflag ()); break; }}}} @data class ThreadDemo implements Runnable{public Boolean flag = false; @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println("ThreadDemo thread modified flag = "+ isFlag()); }}Copy the code

This code is simple: a ThreadDemo class inherits Runnable to create a thread. It has a member variable flag false, overrides the run method, sets flag to true, and an output statement. Then the main thread of the main method reads the flag. If flag is true, the while loop is broken, otherwise the loop is dead. The thread below has changed its flag to true, the main thread should read true, and the loop should end. Take a look at the results:

The results

As you can see from the diagram, the program does not end, which is an infinite loop. The main thread reads a false flag, but another thread has changed the flag to true and printed it out. This is a memory visibility problem.

  • Memory visibility problem: When multiple threads operate on shared data, they are not visible to each other.

Take a look at the image below to understand the code above:

The illustration

To solve this problem, add a lock. As follows:

While (true){synchronized (threadDemo){if (threaddemo.isflag ()){system.out.println (" main thread read flag = "+ threadDemo.isFlag()); break; }}}Copy the code

The lock allows the while loop to read from main memory each time, thus reading true. But once a lock is added, only one thread can access it at a time, and when one thread holds the lock, the others block, making it very inefficient. If you don’t want to lock and you want to resolve memory visibility issues, you can use the volatile keyword.

2. Volatile keyword:

Resources: sourl.cn/sx6zLt

  • Usage:

Volatile keyword: Keeps data visible in memory when multiple threads operate on shared data. Using this keyword to modify shared data will flush the thread cache to main memory in a timely manner. So in cases where locks are not used, volatile can be used. As follows:

public  volatile boolean flag = false;
Copy the code

This solves the memory visibility problem.

  • The difference between volatile and synchronized: Volatile is not mutually exclusive (when one thread holds a lock and no other thread can access it, this is mutually exclusive). Volatile is not atomic.

Atomicity

1. Understand atomicity: Volatile is not atomic, so what is atomic? Let’s start with the following code:

public class TestIcon {
    public static void main(String[] args){
        AtomicDemo atomicDemo = new AtomicDemo();
        for (int x = 0;x < 10; x++){
            new Thread(atomicDemo).start();
        }
    }
}

class AtomicDemo implements Runnable{
    private int i = 0;
    public int getI(){
        return i++;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getI());
    }
}
Copy the code

This code is just saying i++ in the run method, and then starting ten threads to access it. Check out the results:

The results

As you can see, duplicate data appears. There are obviously multithreaded safety issues, or atomicity issues. The so-called atomicity is that the operation cannot be further subdivided, and i++ operation is divided into read and rewrite three steps, as follows:

int temp = i;
i = i+1;
i = temp;
Copy the code

So i++ is obviously not an atomic operation. For the above 10 threads i++, the memory diagram is as follows:

The illustration

Here, it looks like the same memory visibility problem as above. Would the volatile keyword be sufficient? No, because volatile means that all threads are manipulating data in main memory, but they are not mutually exclusive. For example, two threads reading zeros in main memory at the same time, then incrementing and writing to main memory at the same time, the result will still be duplicate data.

2, atomic variables: after JDK 1.5, Java provides atomic variables, in Java. Util. Concurrent. The atomic package. Atomic variables have the following characteristics:

  • Volatile ensures memory visibility.
  • CAS algorithm is used to ensure atomicity.

3. CAS algorithm: CAS algorithm is supported by computer hardware for concurrent operations on shared data. CAS contains three operands:

  • Memory value V
  • Forecasts A
  • Update the value B

B is assigned to V if and only if V==A, that is, V= B, otherwise nothing is done. For the above i++ problem, the CAS algorithm does this: first, V is 0 in main memory, and then estimates A to be 0, since there is no operation yet, V=B, so increment, and change the value in main memory to 1. It doesn’t matter if the second thread reads A 0 in main memory, because the estimate is now 1, and V is not equal to A, so it doesn’t do anything.

Use atomic variables to improve i++ : atomic variables are used in the same way as wrapped classes, as follows:

 //private int i = 0;
 AtomicInteger i = new AtomicInteger();
 public int getI(){
     return i.getAndIncrement();
 }
Copy the code

Just these two things.

Three, lock segmentation mechanism

Resources: sourl.cn/sx6zLt

Since JDK 1.5, several concurrent container classes have been provided in the java.util.Concurrent package to improve the performance of synchronous container classes. The main one is ConcurrentHashMap.

ConcurrentHashMap: ConcurrentHashMap is a thread-safe hash table. We know that HashMap is thread-safe, Hash Table locks are thread-safe, so it’s inefficient. A HashTable lock locks the entire hash table. When multiple threads access a HashTable, only one thread can access it at a time. So after JDK1.5, ConcurrentHashMap was provided, which uses the lock fragmentation mechanism.

ConcurrentHashMap lock block

As shown in the figure above, ConcurrentHashMap is divided into 16 segments by default, each of which corresponds to a Hash table and has its own lock. This allows one Segment per thread to be accessed in parallel, which improves efficiency. This is lock segmentation. ** However, Java 8 has been updated to include CAS instead of lock segmentation.

2. Usage: The java.util.concurrent package also provides an implementation of Collection designed for use in multithreaded contexts: ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet, CopyOnWriteArrayList and CopyOnWriteArraySet. ConcurrentHashMap is generally superior to synchronous HashMap and ConcurrentSkipListMap is generally superior to synchronous TreeMap when many threads are expected to access a given collection. CopyOnWriteArrayList is superior to a synchronized ArrayList when the expected readings and traversals are much greater than the number of updates to the list. Here’s a look at some usage:

public class TestConcurrent { public static void main(String[] args){ ThreadDemo2 threadDemo2 = new ThreadDemo2(); for (int i=0; i<10; i++){ new Thread(threadDemo2).start(); Private static List<String> List = private static List<String> List = Collections.synchronizedList(new ArrayList<>()); Static {list.add("aaa"); list.add("bbb"); list.add("ccc"); } @Override public void run() { Iterator<String> iterator = list.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); / / read the list. The add (" DDD "); // write}}}Copy the code

Ten threads access the collection concurrently, reading and adding data to the collection. Running this code will report an error and modify the exception concurrently.

Concurrent modification exception

Change the collection creation method to:

private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
Copy the code

There will be no concurrent modification exceptions. Because this is write and copy, generating a new one each time, it can be very expensive if there are a lot of add operations, which is suitable for a lot of iteration operations.

Four, atresia

The java.util.Concurrent package provides a variety of concurrent container classes to improve the performance of synchronous containers. ContDownLatch is a synchronization helper class that performs an operation until all other threads have completed the operation. This is called a latching. Look at the following code:

public class TestCountDownLatch { public static void main(String[] args){ LatchDemo ld = new LatchDemo(); long start = System.currentTimeMillis(); for (int i = 0; i<10; i++){ new Thread(ld).start(); } long end = System.currentTimeMillis(); System.out.println(" end - start "+ "second "); } } class LatchDemo implements Runnable{ private CountDownLatch latch; public LatchDemo(){ } @Override public void run() { for (int i = 0; i<5000; I ++){if (I % 2 == 0){system.out.println (I); }}}}Copy the code

This code consists of 10 threads simultaneously printing an even number up to 5000 and counting the execution time on the main thread. In fact, this does not calculate the execution time of the 10 threads, because the main thread is also executing at the same time as the 10 threads. Maybe the 10 threads are only halfway through the execution and the main thread is already printing the sentence “x seconds”. To calculate the execution time of the 10 threads, the main thread must wait for all 10 threads to complete before executing the main thread. This is where latching comes in. See how to use:

public class TestCountDownLatch { public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(10); LatchDemo ld = new LatchDemo(latch); LatchDemo ld = new LatchDemo(latch); long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { new Thread(ld).start(); } try { latch.await(); } Catch (InterruptedException e) {} Long end = system.currentTimemillis (); System.out.println(" + (end-start)); } } class LatchDemo implements Runnable { private CountDownLatch latch; public LatchDemo(CountDownLatch latch) { this.latch = latch; } @Override public void run() { synchronized (this) { try { for (int i = 0; i < 50000; I ++) {if (I % 2 == 0) {system.out.println (I); } } } finally { latch.countDown(); // decrement}}}} after each executionCopy the code

The above code mainly uses latch.countdown () and latch.await() to implement latches, see the comments above for details.

Five, the way to create a thread – implement Callable interface

Look directly at the code:

public class TestCallable { public static void main(String[] args){ CallableDemo callableDemo = new CallableDemo(); FutureTask<Integer> result = new FutureTask<>(callableDemo); new Thread(result).start(); Try {Integer sum = result.get(); // The result will be printed when the thread above is finished executing. It's like lockdown. All FutureTasks can also be used to block system.out.println (sum); } catch (Exception e) { e.printStackTrace(); } } } class CallableDemo implements Callable<Integer>{ @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i<=100; i++){ sum += i; } return sum; }}Copy the code

Now, the difference between the Callable interface and the Runable interface is that Callable has a generic type and its call method has a return value. When used, FutureTask is used to receive the return value. It also waits until the thread has finished calling the GET method and can also be used for locking operations.

Lock Synchronization Lock

Prior to JDK1.5, there were two ways to solve multithreaded security (sychronized implicit locking) :

  • Synchronized code block
  • Synchronized methods

After JDK1.5, there is a more flexible approach (Lock explicit locking) :

  • Synchronization lock

Lock is unlocked by using the Lock () method and unlocked by using the unlock() method. To ensure that the lock can be released, all unlock methods are executed in finally.

Take a look at ticket sales:

public class TestLock { public static void main(String[] args) { Ticket td = new Ticket(); New Thread(td, "window 1").start(); New Thread(td, "window 2").start(); New Thread(td, "window 3").start(); } } class Ticket implements Runnable { private int ticket = 100; @Override public void run() { while (true) { if (ticket > 0) { try { Thread.sleep(200); } catch (Exception e) {} system.out.println (thread.currentThread ().getName() + "+ (--ticket)); }}}}Copy the code

Multiple threads operate on the shared data ticket at the same time, so thread-safety issues can occur. The same ticket may be sold several times or the number of votes may be negative. We used to use synchronized blocks and synchronized methods to solve the problem. Now let’s see how to solve the problem with synchronized locks.

class Ticket implements Runnable { private Lock lock = new ReentrantLock(); Private int ticket = 100; @Override public void run() { while (true) { lock.lock(); Try {if (ticket > 0) {try {thread.sleep (200); } catch (Exception e) {} system.out.println (thread.currentThread ().getName() + "+ (--ticket)); } }finally { lock.unlock(); // Release lock}}}}Copy the code

Create a lock object, use lock() to lock it, and unlock() to unlock it.

7. Wake-up waiting mechanism

1. False wake up problem: The production and consumption pattern is a classic case of wake-up mechanism, see the following code:

public class TestProductorAndconsumer { public static void main(String[] args){ Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); New Thread(productor," productor ").start(); New Thread(consumer," consumer B").start(); {private int product = 0; Public synchronized void get(){if(product >= 10){system.out.println (" product is full "); }else { System.out.println(Thread.currentThread().getName()+":"+ (++product)); }} public synchronized void sell(){if (product <= 0){system.out.println (" out "); }else { System.out.println(Thread.currentThread().getName()+":"+ (--product)); }} // class implements Runnable{private Clerk implements Runnable; public Productor(Clerk clerk){ this.clerk = clerk; } @Override public void run() { for (int i = 0; i<20; i++){ clerk.get(); }} // Consumer implements Runnable{private Consumer implements Runnable; public Consumer(Clerk clerk){ this.clerk = clerk; } @Override public void run() { for (int i = 0; i<20; i++){ clerk.sell(); }}}Copy the code

This is the case of production and consumption mode, where there is no wake-up mechanism, and the operation result is that it will continue to consume even if the product is out of stock, it will print “out of stock” all the time, even if the product is full, it will continue to purchase. Improved with wait-and-wake mechanism:

// class Clerk{private int product = 0; Public synchronized void get(){if(product >= 10){system.out.println (" product is full "); try { this.wait(); Catch (InterruptedException e) {e.printStackTrace(); } }else { System.out.println(Thread.currentThread().getName()+":"+ (++product)); this.notifyAll(); }} public synchronized void sell(){if (product <= 0){system.out.println (" out "); try { this.wait(); Catch (InterruptedException e) {e.printStackTrace(); } }else { System.out.println(Thread.currentThread().getName()+":"+ (--product)); this.notifyAll(); }}}Copy the code

In this way, the above problems will not occur. When there is no production, production is full to inform consumption, consumption is finished to inform production. But this is still a bit of a problem, so change the above code as follows:

If (product >= 1){system.out.println (" product full "); . public void run() { try { Thread.sleep(200); // Sleep for 0.2 seconds} catch (InterruptedException e) {e.printStackTrace(); } for (int i = 0; i<20; i++){ clerk.sell(); }}Copy the code

I made these two changes, ran it again, and found that it was fine, but the program never stopped. This happens because one thread is waiting and another thread has no chance to execute. It cannot wake up the waiting thread, so the program cannot end. The solution is to remove the else from the get and sell methods instead of wrapping the else around them. But even then, if you add two more threads, you will have a negative number.

New Thread(productor, "productor ").start(); New Thread(consumer, "consumer D").start();Copy the code

Running results:

The results

One consumer thread grabs the right to execute, finds that product is 0, and waits, and then another consumer thread grabs the right to execute, product is 0, and waits again, and then the two consumer threads are waiting at the same place. Then, when the producer produces a product, it wakes up the two consumers and finds that product is 1 and consumes it at the same time, resulting in 0 and -1. This is a false awakening. The solution is to change the “if” judgment to “while.” As follows:

Public synchronized void get() {while (product >= 1) {system.out.println (" product is full "); try { this.wait(); Catch (InterruptedException e) {e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + (++product)); this.notifyAll(); Public synchronized void sell() {while (product <= 0) {// Synchronized void sell() { Wait methods should always use system.out.println (" out of stock ") in loops; try { this.wait(); Catch (InterruptedException e) {e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + (--product)); this.notifyAll(); // We can sell it if we don't need it.Copy the code

Just change “if” to “while,” and check each time, and you’re done.

2, use Lock Lock to wait for wake up:

class Clerk { private int product = 0; // Share data private Lock Lock = new ReentrantLock(); Private Condition Condition = lock.newCondition(); Public void get() {// count lock.lock(); // try {while (product >= 1) {system.out.println (" product is full "); try { condition.await(); If (InterruptedException e) {}} system.out.println (thread.currentThread ().getName() + ":" + (++product));  condition.signalAll(); }finally {lock.unlock(); }} public void sell() {// sell(); // try {while (product <= 0) {system.out.println (" out of stock "); try { condition.await(); Catch (InterruptedException e) {e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ":" + (--product)); condition.signalAll(); }finally {lock.unlock(); // Release the lock}}}Copy the code

With lock synchronization, the sychronized keyword is not needed, and the LOCK object and condition instance need to be created. The await(), signal(), and signalAll() methods of condition correspond to wait(), notify(), and notifyAll() methods, respectively.

3. Alternate threads in order: First, let’s look at a question:

Write A program, open 3 threads, the ID of these three threads are respectively A, B, C, each thread will print its ID on the screen 10 times, the output results must be shown in order. Such as: ABCABCABC... In turn, the recursiveCopy the code

Analysis:

Threads are preemptive and alternate in order, so you have to implement thread communication, which is wake-up. You can use synchronous methods, or you can use synchronous locks.Copy the code

Coding implementation:

public class TestLoopPrint { public static void main(String[] args) { AlternationDemo ad = new AlternationDemo(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { ad.loopA(); } } }, "A").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { ad.loopB(); } } }, "B").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { ad.loopC(); } } }, "C").start(); } } class AlternationDemo { private int number = 1; Private Lock Lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); public void loopA() { lock.lock(); try { if (number ! = 1) {// make condition1.await(); } System.out.println(Thread.currentThread().getName()); // Print number = 2; condition2.signal(); } catch (Exception e) { } finally { lock.unlock(); } } public void loopB() { lock.lock(); try { if (number ! = 2) {// make condition2.await(); } System.out.println(Thread.currentThread().getName()); // Print number = 3; condition3.signal(); } catch (Exception e) { } finally { lock.unlock(); } } public void loopC() { lock.lock(); try { if (number ! = 3) {// make condition3.await(); } System.out.println(Thread.currentThread().getName()); // Print number = 1; condition1.signal(); } catch (Exception e) { } finally { lock.unlock(); }}}Copy the code

The above code meets the requirements. Create three threads that call the loopA, loopB, and loopC methods, each of which uses condition to communicate.

ReadWriterLock Read/write lock

When we read data, multiple threads can read data at the same time without any problems, but when we write data, if multiple threads are writing data at the same time, which thread is writing data? So, if you have two threads, write/read needs to be mutually exclusive, but read doesn’t need to be mutually exclusive. Read/write locks can be used at this point. See the examples:

public class TestReadWriterLock { public static void main(String[] args){ ReadWriterLockDemo rw = new ReadWriterLockDemo(); New Thread(new Runnable() {@override public void run() {rw.set((int) math.random ()*101); } },"write:").start(); for (int i = 0; i<100; I++){Runnable Runnable = () -> rw.get(); Thread thread = new Thread(runnable); thread.start(); } } } class ReadWriterLockDemo{ private int number = 0; private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); Public void get(){readwritelock.readlock ().lock(); Println (thread.currentThread ().getName()+":"+number); }finally { readWriteLock.readLock().unlock(); Public void set(int number){readWriteLock.writelock ().lock(); try { System.out.println(Thread.currentThread().getName()); this.number = number; }finally { readWriteLock.writeLock().unlock(); }}}Copy the code

This is how the read/write lock is used. The above code implements the operation of one thread writing and one hundred threads reading simultaneously.

Thread pools

When we use a thread, we need to create a new one and then destroy it again. This frequent creation and destruction also consumes resources, so we provide thread pools. This is similar to connection pooling. Connection pooling is designed to avoid frequent creation and release of connections, so there are a certain number of connections in a connection pool that are taken out of the pool when needed and returned to the pool when used up. The same goes for thread pools. The thread pool has a thread queue that holds all the threads in the wait state. Here’s how to use it:

public class TestThreadPool { public static void main(String[] args) { ThreadPoolDemo tp = new ThreadPoolDemo(); / / 1. Create a thread pool ExecutorService pool = Executors. NewFixedThreadPool (5); Submit (tp); // submit(tp); //3. Disable thread pool pool.shutdown(); } } class ThreadPoolDemo implements Runnable { private int i = 0; @Override public void run() { while (i < 100) { System.out.println(Thread.currentThread().getName() + ":" + (i++)); }}}Copy the code

Thread pool usage is simple in three steps. First create the thread pool by using the tool class Executors, then allocate tasks to the thread pool, and finally shut down the thread pool.