Gold, Silver and Ten, Leak Detection and Defect Completion: Java Thread Synchronization and Implementation

Preface
Why use Java thread synchronization? Java allows multi-thread concurrency control. When multiple threads operate a shared resource variable at the same time, it will result in inaccurate data and conflict with each other. Therefore, synchronization lock is added to avoid being called by other threads before the thread completes its operation, thus ensuring the uniqueness and accuracy of the variable.

But the fundamental of concurrent programming is to make the correct communication between threads. Two of the more important key points are as follows:

Thread communication: focusing on several ways of thread synchronization;
Correct communication: Focus on thread security issues;
Java provides many thread synchronization operations, such as: synchronized keywords, wait / notify All, ReentrantLock, Condition, some tool classes under concurrent packages, Semaphore, ThreadLocal, AbstractQueued Synchronizer, etc. This paper mainly describes the use of these synchronization methods and their advantages and disadvantages.

1 ReentrantLock can be re-locked
Since JDK5, the Lock interface and one of its implementation classes, ReentrantLock, have been added. ReentrantLock ReentrantLock is a lock object built into the J.U.C package, which can be used to achieve synchronization. The basic usage methods are as follows:

public class ReentrantLockTest {

private ReentrantLock lock = new ReentrantLock();
public void execute() {
    lock.lock();
    try {
        System.out.println(Thread.currentThread().getName() + " do something synchronize");
        try {
            Thread.sleep(5000l);
        } catch (InterruptedException e) {
            System.err.println(Thread.currentThread().getName() + " interrupted");
            Thread.currentThread().interrupt();
        }
    } finally {
        lock.unlock();
    }
}
public static void main(String[] args) {
    ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            reentrantLockTest.execute();
        }
    });
    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            reentrantLockTest.execute();
        }
    });
    thread1.start();
    thread2.start();
}

}
Copy code
The above example shows that only one thread can execute the execute method at the same time period. The output is as follows:

Thread-0 do something synchronize
// After 5 seconds, enter the following
Thread-1 do something synchronize
Copy code
The significance of reentrant representation in reentrant locks is that for the same thread, the method of locking can continue to be invoked without being suspended. A counter is maintained inside the reentrant lock. For the same thread, lock method is called, counter + 1, unlock method is called, counter - 1.

To illustrate the meaning of reentrant, for example, calling another locking method anotherLock in one locking method execute is not suspended, but can be called directly (counter + 1 when execute method is called, then anotherLock method is called internally, counter + 1 becomes 2):

public void execute() {

lock.lock();
try {
    System.out.println(Thread.currentThread().getName() + " do something synchronize");
    try {
        anotherLock();
        Thread.sleep(5000l);
    } catch (InterruptedException e) {
        System.err.println(Thread.currentThread().getName() + " interrupted");
        Thread.currentThread().interrupt();
    }
} finally {
    lock.unlock();
}

}
public void anotherLock() {

lock.lock();
try {
    System.out.println(Thread.currentThread().getName() + " invoke anotherLock");
} finally {
    lock.unlock();
}

}
Copy code
Output:

Thread-0 do something synchronize
Thread-0 invoke anotherLock
// After 5 seconds, enter the following
Thread-1 do something synchronize
Thread-1 invoke anotherLock
Copy code
2 synchronized
Similar to ReentrantLock, synchronized also supports reentrant locks. But it's a keyword, a syntax-level synchronization called built-in locks:

public class SynchronizedKeyWordTest {

public synchronized void execute() {
        System.out.println(Thread.currentThread().getName() + " do something synchronize");
    try {
        anotherLock();
        Thread.sleep(5000l);
    } catch (InterruptedException e) {
        System.err.println(Thread.currentThread().getName() + " interrupted");
        Thread.currentThread().interrupt();
    }
}
public synchronized void anotherLock() {
    System.out.println(Thread.currentThread().getName() + " invoke anotherLock");
}
public static void main(String[] args) {
    SynchronizedKeyWordTest reentrantLockTest = new SynchronizedKeyWordTest();
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            reentrantLockTest.execute();
        }
    });
    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            reentrantLockTest.execute();
        }
    });
    thread1.start();
    thread2.start();
}

}
Copy code
The output is the same as ReentrantLock, and this example illustrates how built-in locks work. The synchronized keyword can also modify a static method, at which point if the static method is called, the entire class will be locked. Click Free "Get Java Architecture Information"

Synchronization is a high overhead operation, so the content of synchronization should be minimized. Usually, it is not necessary to synchronize the whole method. synchronized code blocks are used to synchronize key code.

Compared with ReentrantLock, synchronized has several limitations:

You can't set timeout when locking. ReentrantLock provides a tryLock method, which can set the timeout time. If the time exceeds and the lock is not acquired, it will give up, but synchronized does not have this function.
ReentrantLock can use multiple conditions, while synchronized can only have one
Can't interrupt a thread trying to get a lock;
ReentrantLock can choose fair locks and unfair locks.
ReentrantLock can get the number of threads waiting, counters, etc.
Therefore, Lock's operation is more flexible than synchronized, and Lock provides a variety of ways to acquire locks, including Lock, ReadWriteLock interfaces, ReentrantLock classes and ReentrantReadWriteLock classes that implement these two interfaces.

Considerations about Lock objects and synchronized keyword selection:

It's best to use a mechanism provided by the java.util.concurrent package to help users process all lock-related code.
If the synchronized keyword meets the user's needs, use synchronized because it simplifies the code.
If you need more advanced functionality, use the ReentrantLock class. At this time, you should pay attention to release the lock in time, otherwise deadlock will occur, usually in final code to release the lock.
In terms of performance considerations, if there is no fierce competition for resources, the performance of both is the same, and when the competition for resources is very fierce (that is, there are a lot of threads competing at the same time), the performance of Lock is far better than synchronized. Therefore, in the specific use of the choice should be based on the appropriate circumstances. Click Free "Get Java Architecture Information"

3 Condition conditional object
Condition conditional objects are intended to be used for a thread that has acquired a Lock lock, if it still needs to wait for other conditions to continue executing.

Condition s can replace traditional inter-thread communication, wait() with await(), notify() with signal(), and notifyAll() with signalAll().

Why is the method name not called wait()/notify()/nofityAll()? Because these methods of Object are final and cannot be rewritten!
public class ConditionTest {

public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " run");
                System.out.println(Thread.currentThread().getName() + " wait for condition");
                try {
                    condition.await();
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {
                    System.err.println(Thread.currentThread().getName() + " interrupted");
                    Thread.currentThread().interrupt();
                }
            } finally {
                lock.unlock();
            }
        }
    });
    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " run");
                System.out.println(Thread.currentThread().getName() + " sleep 5 secs");
                try {
                    Thread.sleep(5000l);
                } catch (InterruptedException e) {
                    System.err.println(Thread.currentThread().getName() + " interrupted");
                    Thread.currentThread().interrupt();
                }
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }
    });
    thread1.start();
    thread2.start();
}

}
Copy code
In this example, when thread1 executes to condition.await(), the current thread is suspended until thread2 calls the condition.signalAll() method before thread1 is reactivated to execute.

It should be noted here that thread1 thread releases the lock after calling the await method of Conditions, and then immediately joins the waiting queue of Conditions. Because thread1 releases the lock, thread2 gets the lock and executes it. After thread2 executes the signalAll method, the waiting queue thread1 in Conditions is taken out and added to AQS. After the execution of thread2, the lock is released. Thead1 is already in the AQS waiting queue, so thread1 is awakened and continues to execute.
Conditions can be implemented in traditional thread communication mode. The strength of Conditions is that it can create different conditions for multiple threads.

Note that Conditions are bound to Lock. To create a Lock Conditions, you must use the new Condition () method.
4 wait & notify / notify All approach
The state transition diagram of Java threads and related methods are as follows:

Photo Description (up to 50 words)

In the figure, some methods of red box identification can be considered outdated and no longer used. The methods in the figure above can participate in thread synchronization, as follows:

Wait, notify, notifyAll methods: methods that can be used for communication in threads. If the wait method is called in the thread, it will be blocked, and only wait for another thread to call the notify method of the same object as the wait. There's a special place to call wait or notify if you need to get the lock, that is to say, you need to do the above in the synchronization block.
The wait/notify All approach works the same way as the ReentrantLock/Condition approach.
Every object in Java has a built-in lock, calling wait in the built-in lock. The notify method is equivalent to the await and signalAll methods that call the Conditions Condition object of the lock.
public class WaitNotifyAllTest {

public synchronized void doWait() {
    System.out.println(Thread.currentThread().getName() + " run");
    System.out.println(Thread.currentThread().getName() + " wait for condition");
    try {
        this.wait();
        System.out.println(Thread.currentThread().getName() + " continue");
    } catch (InterruptedException e) {
        System.err.println(Thread.currentThread().getName() + " interrupted");
        Thread.currentThread().interrupt();
    }
}
public synchronized void doNotify() {
    try {
        System.out.println(Thread.currentThread().getName() + " run");
        System.out.println(Thread.currentThread().getName() + " sleep 5 secs");
        Thread.sleep(5000l);
        this.notifyAll();
    } catch (InterruptedException e) {
        System.err.println(Thread.currentThread().getName() + " interrupted");
        Thread.currentThread().interrupt();
    }
}
public static void main(String[] args) {
    WaitNotifyAllTest waitNotifyAllTest = new WaitNotifyAllTest();
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            waitNotifyAllTest.doWait();
        }
    });
    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            waitNotifyAllTest.doNotify();
        }
    });
    thread1.start();
    thread2.start();
}

}
Copy code
It is important to note that when calling the wait/notifyAll method, you must obtain the lock of the current thread, otherwise an IllegalMonitorStateException exception will occur.
join method: The main function of this method is to execute the run method in the thread after it has finished.
package com.thread.simple;

public class ThreadJoin {

public static void main(String[] args) {
    Thread thread= new Thread(new Runnable() {
          @Override
          public void run() {
               System.err.println("thread"+Thread.currentThread().getId()+" Print information");
          }
    });
    thread.start();
 
    try {
        thread.join();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.err.println("Print information by main thread");    
}

}
Copy code
yield method: The scheduling method of the thread itself. When the thread is used, it can call the method when the run method is finished, informing the thread that it can transfer CPU resources.
public class Test1 {

public static void main(String[] args) throws InterruptedException {  
    new MyThread("Lower", 1).start();  
    new MyThread("intermediate", 5).start();  
    new MyThread("senior", 10).start();  
}  

}
class MyThread extends Thread {

public MyThread(String name, int pro) {  
    super(name);// Set the name of the thread  
    this.setPriority(pro);// set priority  
}  
@Override  
public void run() {  
    for (int i = 0; i < 30; i++) {  
        System.out.println(this.getName() + "Line number 1" + i + "Sub execution!");  
        if (i % 5 == 0)  
            Thread.yield();  
    }  
}  

}
Copy code
Sleep method: sleep(millis) enables threads to sleep for a period of time. This method can not be awakened within a specified time, but also does not release the object lock.
/**

  • You can clearly see that the printed numbers are slightly spaced in time.

*/
public class Test1 {

public static void main(String[] args) throws InterruptedException {  
    for(int i=0;i<100;i++){  
        System.out.println("main"+i);  
        Thread.sleep(100);  
    }  
}  

}
Copy code
The sleep method tells the operating system that the thread scheduler does not need to allocate execution time slices for the thread at least for a specified time, and does not release the lock (if the lock is currently held). In fact, you don't need to hold any locks when calling the sleep method.
Therefore, the sleep method does not need to hold any form of lock, nor does it need to be wrapped in synchronized.
5 ThreadLocal
ThreadLocal is a way to synchronize threads by putting variables locally. For example, SimpleDateFormat is not a thread-safe class and can be synchronized using ThreadLocal as follows:

public class ThreadLocalTest {

private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};
public static void main(String[] args) {
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            Date date = new Date();
            System.out.println(dateFormatThreadLocal.get().format(date));
        }
    });
    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            Date date = new Date();
            System.out.println(dateFormatThreadLocal.get().format(date));
        }
    });
    thread1.start();
    thread2.start();
}

}
Copy code
Why is SimpleDateFormat not a thread-safe class? Specifically, please refer to:

https://blog.csdn.net/zdp072/...
https://blog.csdn.net/zq60231...
Comparing ThreadLocal with Synchronization:

ThreadLocal and synchronization mechanism are both used to solve the access conflict problem of the same variable in multithreading.
The former uses the method of "space for time", while the latter uses the method of "time for space".
6 volatile modifier variable
The volatile keyword provides a lock-free mechanism for accessing domain variables. Using volatile to modify a domain is equivalent to telling the virtual machine that the domain may be updated by other threads, so every time you use the domain, you need to recalculate it instead of using the value in the register. Volatile does not provide any atomic operations, nor can it be used to repair it. A variable of type final.

// Give only the code to be modified, and the rest is the same as above.
public class Bank {

//Variables requiring synchronization plus volatile
private volatile int account = 100;
public int getAccount() {
    return account;
}
//There is no need for synchronized 
public void save(int money) {
    account += money;
}


Copy code
The asynchronism problem in multithreading mainly occurs in the reading and writing of the domain. If the domain itself is allowed to avoid this problem, there is no need to modify the method of operating the domain. With final domains, lock-protected domains and volatile domains can avoid the problem of asynchronism. Click Free "Get Java Architecture Information"

7 Semaphore semaphore
Semaphore semaphores are used to control the number of specific resources accessed at the same time. Similar to the concept of connection pool, it ensures that resources can be used reasonably. You can initialize the number of resources using the constructor:

public class SemaphoreTest {

private static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
    for(int i = 0; i < 5; i ++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " " + new Date());
                    Thread.sleep(5000l);
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.err.println(Thread.currentThread().getName() + " interrupted");
                }
            }
        }).start();
    }
}

}
Copy code
Output:

Thread-1 Mon Apr 18 18:03:46 CST 2016
Thread-0 Mon Apr 18 18:03:46 CST 2016
Thread-3 Mon Apr 18 18:03:51 CST 2016
Thread-2 Mon Apr 18 18:03:51 CST 2016
Thread-4 Mon Apr 18 18:03:56 CST 2016
Copy code
8 Tool classes under concurrent packages
8.1 CountDownLatch

CountDownLatch is a counter, and its construction method needs to set a value to set the number of counts. Each time the countDown() method is called, the counter subtracts 1, and CountDownLatch blocks the thread that calls the await() method until the counter value changes to zero.

public class CountDownLatchTest {

public static void main(String[] args) {
    CountDownLatch countDownLatch = new CountDownLatch(5);
    for(int i = 0; i < 5; i ++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " " + new Date() + " run");
                try {
                    Thread.sleep(5000l);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }
        }).start();
    }
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("all thread over");
}

}
Copy code
Output:

Thread-2 Mon Apr 18 18:18:30 CST 2016 run
Thread-3 Mon Apr 18 18:18:30 CST 2016 run
Thread-4 Mon Apr 18 18:18:30 CST 2016 run
Thread-0 Mon Apr 18 18:18:30 CST 2016 run
Thread-1 Mon Apr 18 18:18:30 CST 2016 run
all thread over
Copy code
8.2 CyclicBarrier

Cyclic Barrier blocks the calling thread until the condition is satisfied, and the blocked thread is opened at the same time.

When the await() method is called, the thread is blocked. When the number of threads calling await() reaches the barrier number, the main thread cancels the state of all blocked threads.
In the construction method of Cyclic Barrier, you can also set up a barrier action. After all the barriers have arrived, a thread is started to run the code.
public class CyclicBarrierTest {

public static void main(String[] args) {
    Random random = new Random();
    CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    for(int i = 0; i < 5; i ++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int secs = random.nextInt(5);
                System.out.println(Thread.currentThread().getName() + " " + new Date() + " run, sleep " + secs + " secs");
                try {
                    Thread.sleep(secs * 1000);
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " " + new Date() + " runs over");
            }
        }).start();
    }
}

}
Copy code
Cyclic Barrier is more flexible than CountDownLatch in that it can be recycled and reset() can be used to reset counters when thread interruption occurs.

9 Thread Synchronization Using Atomic Variables
Sometimes the fundamental reason for thread synchronization is that the operations on common variables are not atomic. So what is atomic operation? Click Free "Get Java Architecture Information"

Atomic operation refers to the operation of reading variable values, modifying variable values and preserving variable values as a whole, that is, these actions are either completed at the same time or not.
Tool classes for creating atomic type variables are provided in the java.util.concurrent.atomic package, which can simplify thread synchronization. For example, Atomic Integer updates the value of int atomically:

class Bank {

private AtomicInteger account = new AtomicInteger(100);
public AtomicInteger getAccount() {
    return account;
}
public void save(int money) {
    account.addAndGet(money);
}

}
Copy code
10 AbstractQueuedSynchronizer
AQS is the basis of many synchronization tool classes, such as fair and unfair locks in ReentrantLock, fair and unfair locks in Semaphore, and locks in CountDownLatch, whose underlying layers are accomplished using AbstractQueued Synchronizer.

Implement an exclusive lock based on Abstract Queued Synchronizer customization:

public class MySynchronizer extends AbstractQueuedSynchronizer {

@Override
protected boolean tryAcquire(int arg) {
    if(compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}
@Override
protected boolean tryRelease(int arg) {
    setState(0);
    setExclusiveOwnerThread(null);
    return true;
}
public void lock() {
    acquire(1);
}
public void unlock() {
    release(1);
}
public static void main(String[] args) {
    MySynchronizer mySynchronizer = new MySynchronizer();
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            mySynchronizer.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " run");
                System.out.println(Thread.currentThread().getName() + " will sleep 5 secs");
                try {
                    Thread.sleep(5000l);
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {
                    System.err.println(Thread.currentThread().getName() + " interrupted");
                    Thread.currentThread().interrupt();
                }
            } finally {
                mySynchronizer.unlock();
            }
        }
    });
    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            mySynchronizer.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " run");
            } finally {
                mySynchronizer.unlock();
            }
        }
    });
    thread1.start();
    thread2.start();
}

}
Copy code
11 Thread synchronization using blocking queues
The first several synchronization methods are based on the underlying thread synchronization, but in the actual development, we should try to stay away from the underlying structure. This section mainly uses LinkedBlockingQueue to synchronize threads.

Linked blocking queue is a linked list-based queue, FIFO, with arbitrary range of blocking queues.
package com.xhj.thread;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
/**

  • Using Blocking Queue to Realize Thread Synchronization LinkedBlocking Queue

*/
public class BlockingSynchronizedThread {

/**
 * Define a blocking queue to store the goods produced
 */
private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
/**
 * Define the number of goods produced
 */
private static final int size = 10;
/**
 * Define the start thread flag to start the production thread at 0; start the consumption thread at 1:00
 */
private int flag = 0;
private class LinkBlockThread implements Runnable {
    @Override
    public void run() {
        int new_flag = flag++;
        System.out.println("Startup thread " + new_flag);
        if (new_flag == 0) {
            for (int i = 0; i < size; i++) {
                int b = new Random().nextInt(255);
                System.out.println("Producing commodities:" + b + "Number");
                try {
                    queue.put(b);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("There are also goods in the warehouse:" + queue.size() + "individual");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        } else {
            for (int i = 0; i < size / 2; i++) {
                try {
                    int n = queue.take();
                    System.out.println("The consumer bought it." + n + "Commodity No.");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("There are also goods in the warehouse:" + queue.size() + "individual");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
        }
    }
}
public static void main(String[] args) {
    BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
    LinkBlockThread lbt = bst.new LinkBlockThread();
    Thread thread1 = new Thread(lbt);
    Thread thread2 = new Thread(lbt);
    thread1.start();
    thread2.start();
}

}
Source:

Keywords: Java Programming REST

Added by gladius on Fri, 27 Sep 2019 17:10:28 +0300