Knowledge about multithreaded concurrency and thread security is organized as follows:
- How Threads Ensure Security
- How to publish objects securely
- What are the means of thread security
- Explanation of JUC components
- How to improve thread scheduling
1. How do threads ensure security?
- What is Thread Security
This class is thread safe when multiple threads access a class, regardless of how the runtime environment is scheduled or how these processes will execute alternately, and no additional synchronization or collaboration is required in the main code.
- Three characteristics of thread security
Atomicity, order, visibility
Atomicity: Provides mutually exclusive access to which only one thread can operate at a time.
Orderliness: A thread observes the order in which instructions are executed in other threads. Due to the existence of instruction reordering, this observation is generally disordered.
Visibility: Changes to main memory made by one thread can be observed by other threads in a timely manner.
Characteristic | operation |
Atomicity | synchronized blocks of code guarantee serial execution |
Orderliness | Can be implemented by volatile / synchronized (at most one thread can lock a variable) |
visibility | Can be achieved by final (no modification), volatile (forced update + read main memory), and synchronized (all modified data is refreshed to main memory when unlock, and data is reloaded from main memory when lock) |
- Atomic Package
Java has provided the java.util.concurrent.atomic package since JDK1.5 to facilitate programmers to perform atomic operations without locks in a multithreaded environment.The bottom level of the atomic variable uses the atomic instructions provided by the processor, but different CPU architectures may provide different atomic instructions or may require some form of internal lock, so this method does not absolutely guarantee that the thread is not blocked.
Upper Code
package Atomic; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; //Thread Security public class AtomicIntegerDemo { private final static int clientTotal=5000;//Total Threads private final static int threadTotal=200;//Number of threads per pass static AtomicInteger ai=new AtomicInteger(0); public static void main(String[] args) { //Thread Pool ExecutorService executorService= Executors.newCachedThreadPool(); //Generate semaphore final Semaphore semaphore=new Semaphore(threadTotal);//20 passes at a time final CountDownLatch latch=new CountDownLatch(clientTotal);//Counter for(int i=0;i<clientTotal;i++) { executorService.execute(() -> { try { semaphore.acquire();//Apply/Obtain a license //incrementAndGet() //-->1.unsafe.getAndAddInt //-->2.this.compareAndSwapInt(CAS) //-->3.native Method //2->3,Actually, it is the process of checking working and main memory(CAS Of particular importance) ai.incrementAndGet();//Data plus 1 semaphore.release();//Release License } catch (Exception e) { System.out.println("execption:" + e); } latch.countDown();//Counter minus 1 }); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } executorService.shutdown();//Close Threads System.out.println(ai.get()); } }
- Synchronized (lock)
synchronized: Depending on the JVM, the JVM automatically locks and unlocks
Lock: Depends on CPU instructions, requires manual unlock, ReentrantLock
Synchroized code
package Sync; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SynchronizedDemo { //Modify a whole method private synchronized void test1(){ for(int i=0;i<10;i++){ System.out.println("Test1 - "+i); } } //Decorate a block of code private void test2(){ synchronized (this){ for(int i=0;i<10;i++){ System.out.println("Test2 - "+i); } } } public static void main(String[] args) { SynchronizedDemo demo1=new SynchronizedDemo(); ExecutorService executorService= Executors.newCachedThreadPool(); executorService.execute(()->{ demo1.test1(); }); executorService.execute(()->{ demo1.test2(); }); executorService.shutdown(); } }
The corresponding scopes for Synchronized are as follows
Operation method | Scope |
Modify a piece of code | Brace-enclosed code that acts on the called object |
Modification Method | Whole method, acting on the called object |
Modify Static Method | Whole static method, acting on all objects (considering JVM principles) |
Modifier class | Parts enclosed in brackets that act on all objects (from JVM principles) |
Lock code
package Sync; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockDemo { private static int count=0; private final static int threadPoolCount=5000; private final static int threadCount=200; private static Lock lock=new ReentrantLock(); public static void main(String[] args) { ExecutorService executorService= Executors.newCachedThreadPool();//Thread Pool Semaphore semaphore=new Semaphore(threadCount); final CountDownLatch countDownLatch = new CountDownLatch(threadPoolCount); for(int i=0;i<threadPoolCount;i++){ executorService.execute(()->{ try { semaphore.acquire(); add(); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } countDownLatch.countDown(); }); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } executorService.shutdown(); System.out.println(count); } private static void add(){ lock.lock();//locking count++;//Methods requiring synchronization lock.unlock();//Unlock } }
Atomic, synchronized, Lock comparison
Atomic: Maintains normality in times of intense competition, performs better than Lock, but updates only one value
Synnized: Non-interruptable lock, suitable for less competitive, readable, JVM automatically releases resources.
Lock: Interruptible locks, which can be maintained under intense competition, require manual locking and unlocking.
- visibility
Reasons why shared variables are not visible between threads (threads cross-execute, reordering combined with thread cross-execute, values updated by shared variables are not updated in working memory and main memory in a timely manner)
Visibility-synchronized
Two rules of synchronized in the JAVA memory model:
1. You must refresh the latest shared variables to main memory before the thread can unlock
2. When a thread locks, the values of shared variables in working memory are cleared, triggering the need to use shared variables and having to re-read the latest values from main memory
Visibility-volatile
By adding memory barriers and prohibiting reordering optimization
When a volatile variable is written, a store barrier directive is added after the write operation to refresh the shared variable values in local memory to main memory.
When a volatile variable is read, a load barrier directive is added before the read operation to read the shared variable from main memory
package Sync; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class VolatileDemo { private static volatile int count=0; private final static int threadPoolCount=5000; private final static int threadCount=200; public static void main(String[] args) { ExecutorService executorService= Executors.newCachedThreadPool();//Thread Pool Semaphore semaphore=new Semaphore(threadCount); final CountDownLatch countDownLatch = new CountDownLatch(threadPoolCount); for(int i=0;i<threadPoolCount;i++){ executorService.execute(()->{ try { semaphore.acquire(); add(); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } countDownLatch.countDown(); }); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } executorService.shutdown(); System.out.println(count); } private static void add(){ //1.First step acquisition count //2.Step 2+1 //3.hold count Value, returned to main memory count++; //Calculation results show Volatile Not Atomic //Volatile Better use of status tags ( boolean) //Writing to a variable does not depend on the current value //The variable is not included in a variable with other variables } }
- Orderliness
The JAVA memory model allows compilers and processors to reorder instructions, but the reordering process does not affect the execution of single-threaded programs, but it does affect the correctness of multithreaded concurrent execution.
The Eight Principles of happens-before
Procedural Sequence Rule: Within a thread, executes in the order of code.
Lock rule: An unLock operation occurs after a lock operation for a lock.
volatile variable rule: Writing a variable first occurs after reading the variable.
Delivery rule: A action B, B action C, then A action C can be derived.
Thread Start Rule: The Thread object's start() method first sends each action with this thread.
Thread interrupt rule: A call to the thread interrupt() method sends first and the code of the interrupt thread detects an interrupt event.
Thread termination rule: All operation termination detection in a thread can be ended by the Thread.join() method.
Object Termination Rule: The initialization of an object is first sent to the beginning of its finalize() method.