1, Fair lock and unfair lock
- Rendering of unfair lock: it can be seen that in the case of multiple threads, one thread will monopolize and other threads will starve to death. Because the process of thread switching is omitted, the efficiency is relatively high
public void test1(){ test test = new test(); new Thread(()->{ for(int i = 0; i < 10; i++){ test.sale(); } },"aa").start(); new Thread(()->{ for(int i = 0; i < 10; i++){ test.sale(); } },"bb").start(); } //The results are as follows: aa Sold: 10 remaining: 9 aa Sold: 9 remaining: 8 aa Sold: 8 remaining: 7 aa Sold: 7 remaining: 6 aa Sold: 6 remaining: 5 aa Sold: 5 remaining: 4 aa Sold: 4 remaining: 3 aa Sold: 3 remaining: 2 aa Sold: 2 remaining: 1 aa Sold: 1 remaining: 0
- Unfair lock creation (default)
final ReentrantLock lock = new ReentrantLock(); final ReentrantLock lock = new ReentrantLock(false);
- Fair lock rendering: it can be seen that rain and dew will be soaked in multi threads, which will not cause thread monopoly. Due to the increased process of thread switching and determining whether to lock, the efficiency is relatively low
aa Sold: 20 remaining: 19 aa Sold: 19 remaining: 18 aa Sold: 18 remaining: 17 aa Sold: 17 remaining: 16 aa Sold: 16 remaining: 15 aa Sold: 15 remaining: 14 aa Sold: 14 remaining: 13 bb Sold: 13 remaining: 12 aa Sold: 12 remaining: 11 bb Sold: 11 remaining: 10 aa Sold: 10 remaining: 9 bb Sold: 9 remaining: 8 aa Sold: 8 remaining: 7 bb Sold: 7 remaining: 6 aa Sold: 6 remaining: 5 bb Sold: 5 remaining: 4 aa Sold: 4 remaining: 3 bb Sold: 3 remaining: 2 aa Sold: 2 remaining: 1 bb Sold: 1 remaining: 0
2, Reentrant lock
- Definition: reentrant means that a thread has obtained a lock and can obtain the lock again without deadlock
1. Implementation of synchronized (implicit lock)
- Implicit definition: locking and unlocking processes are not directly reflected in the code
- Implementation 1: nested multi-layer locks. Starting from the outside of the lock, if the lock is successfully requested for many times, you can freely enter the nested lock
public void test1(){ Object o = new Object(); new Thread(()->{ synchronized (o){ System.out.println("Outer layer"); synchronized (o){ System.out.println("middle level"); synchronized (o){ System.out.println("Inner layer"); } } } },"aa").start(); } //The results are as follows Outer layer middle level Inner layer
- Implementation 2: recursive lock method, because it is a reentrant lock and can obtain the lock multiple times, it will cause stack overflow
public synchronized void test2(){ test2(); } //The implementation results are as follows java.lang.StackOverflowError at demo.test.test2(test.java:33) at demo.test.test2(test.java:33)
2. Lock (explicit lock) implementation
- Explicit definition: the locking and unlocking process is directly reflected in the code
- Implementation: nested multi-layer locks. The nested inner lock of the second code is not unlocked, and there is no error in the running result. However, in the case of multithreading, it will cause other threads to block and wait
public void test1(){ ReentrantLock lock = new ReentrantLock(true); new Thread(()->{ try { lock.lock(); System.out.println("hello1"); try { lock.lock(); System.out.println("hello2"); }finally { lock.unlock(); } }finally { lock.unlock(); } },"aa").start(); } //The operation results are as follows: hello1 hello2 public void test1(){ ReentrantLock lock = new ReentrantLock(true); new Thread(()->{ try { lock.lock(); System.out.println("hello1"); try { lock.lock(); System.out.println("hello2"); }finally { //lock.unlock(); } }finally { lock.unlock(); } },"aa").start(); } //The operation results are as follows: hello1 hello2
3, Deadlock
- Definition: during the execution of two or more processes, a phenomenon of waiting for each other because of competing for resources. If there is no external intervention, they can no longer be executed
- Deadlock implementation case
static Object object1 = new Object(); static Object object2 = new Object(); public static void main(String[] args) { new Thread(()->{ synchronized (object1){ System.out.println("Hold lock object1,Attempt to acquire lock object2"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (object2){ System.out.println("obtain object2 success"); } } },"aa").start(); new Thread(()->{ synchronized (object2){ System.out.println("Hold lock object2,Attempt to acquire lock object1"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (object1){ System.out.println("obtain object1 success"); } } },"bb").start(); } //The results are as follows Hold lock object1,Attempt to acquire lock object2 Hold lock object2,Attempt to acquire lock object1
- Verify deadlock
1. Execute the jps command jps -l to check the process number (Note: the jps file path is in the java/jdk/bin directory, and it is best to configure it in the environment variable). The screenshot of the effect is as follows: it can be seen that the executed process number is 8796
2. Execute the command jstack 8796 of the stack monitoring tool jstack (provided with the jvm). The screenshot of the effect is as follows: explain the birth and death lock phenomenon
4, Callable interface
1. Difference between callable interface and Runnable interface
- Whether an exception is thrown and whether there is a return value
1. The callable implementation method throws an exception and has a return value. The source code is as follows:
public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * Calculate a result and throw an exception if it is unsuccessful * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
2. The implementation method of runnable will not throw an exception and has no return value. The source code is as follows:
public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
2. Origin of callable thread creation
-
Creation basis
1. It is impossible to create a thread directly. You want to use the new Thread to construct with parameters, but the parameters can be passed to the Runnable interface implementation class, but not to the Callable interface implementation class
2. Based on the above problems, think creatively and find the intermediate class FutureTask associated with Callable and Runnable. The reason is that the implementation class of Runnable interface includes it, and the parametric construction method of this class can also be passed to Callable
3. Understand FutureTask
- Simulation example: three students do arithmetic problems. The difficulty of classmate 1 is the highest, followed by classmate 2, and the difficulty of classmate 3 is the lowest. The calculation order is 123. In order to save time, another thread is opened at classmate 1 with the greatest difficulty. First calculate the problems of classmate 23, and finally summarize the results of classmate 123
- Summary: without affecting the total thread, open another thread to do other things, and finally summarize (Note: only summarize once, for example, do the above questions for the second time, and the calculation results are directly obtained from the first summary, so there is no need to summarize again)
5, Three JUC helper classes
1.CountDownLatch
- Usage Note: this class can set a counter, then use the countDown method to subtract one, use the await method to wait until the counter is not greater than 0, and then continue to execute the statements after the await method
- Use case: after five students leave the classroom one after another, the students on duty can close the door
1. Without using the countDown method, the operation effect is as follows:
public static void main(String[] args) { for(int i = 0; i < 5; i++){ new Thread(()->{ System.out.println(Thread.currentThread().getName() + "The classmate left"); },String.valueOf(i)).start(); } new Thread(()->{ System.out.println(Thread.currentThread().getName() + "lock the door,I am leaving"); },"monitor").start(); } //give the result as follows 0 The classmate left 4 The classmate left 3 The classmate left The monitor locked the door,I am leaving 2 The classmate left 1 The classmate left
2. Use the countDown method, and the effect diagram is as follows:
public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(5); for(int i = 0; i < 5; i++){ new Thread(()->{ System.out.println(Thread.currentThread().getName() + "The classmate left"); latch.countDown(); },String.valueOf(i)).start(); } latch.await(); new Thread(()->{ System.out.println(Thread.currentThread().getName() + "lock the door,I am leaving"); },"monitor").start(); } //give the result as follows 0 The classmate left 4 The classmate left 3 The classmate left 2 The classmate left 1 The classmate left The monitor locked the door,I am leaving
2.CyclicBarrier
- Instructions for use: the first parameter of the construction method is the number of target obstacles. The number of obstacles will be increased by 1 each time. If the number of target obstacles is reached, the statements after cyclicBarrier.await() will be executed. It can be understood as a plus one operation
- Use case: collect 7 dragon balls to summon the divine dragon
private static final int NUMBER = 7; public static void main(String[] args) throws InterruptedException { CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, ()->{ System.out.println("Collect 7 dragon balls to summon the divine dragon"); }); for(int i = 1; i <= 7; i++){ new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+"Xinglong was collected"); cyclicBarrier.await(); }catch (Exception e){ e.printStackTrace(); } }, String.valueOf(i)).start(); } } //The results are as follows: 2 Xinglong was collected 7 Xinglong was collected 5 Xinglong was collected 4 Xinglong was collected 3 Xinglong was collected 1 Xinglong was collected 6 Xinglong was collected Collect 7 dragon balls to summon the divine dragon
3. Screenshot of the difference between the two
4.Semaphore
- Usage Note: the first parameter passed in the construction method of this class is the maximum semaphore. Each semaphore is initialized to one. At most one license can be distributed. Use the acquire method to obtain the license, and the release method to release the license
- Use case: grab parking space
private static final int NUMBER = 7;//Maximum semaphore public static void main(String[] args) throws InterruptedException { Semaphore semaphore = new Semaphore(3); for(int i = 1; i < 6; i++){ new Thread(()->{ try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + ":Seize parking space"); TimeUnit.SECONDS.sleep(new Random().nextInt(4)); System.out.println(Thread.currentThread().getName() + ":Left the parking space"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); } }, String.valueOf(i)).start(); } } //The operation results are as follows 1:Seize parking space 2:Seize parking space 3:Seize parking space 3:Left the parking space 4:Seize parking space 2:Left the parking space 5:Seize parking space 1:Left the parking space 4:Left the parking space 5:Left the parking space
6, Read write lock
1. Pre knowledge
1.1 lock
- Illustration: This is the transfer process. You have 10000 yuan in your account. When you transfer, it will be locked to ensure that the operation will not be affected by other operations. When the transfer operation is successful, unlock it. At this time, it's another person's turn to carry out the transfer operation. It also needs to be locked and unlocked at the end
- Disadvantages: concurrent operations are not supported and can only be executed one by one;
- Advantages: solve various problems in concurrency
1.2 lock
- Illustration: when two people transfer the same account data, the data will be marked with the version number. If one person performs the operation and submits, the data version number will change. When the other person submits, the previous original data version number will be compared with the version number changed at this time. If it is different, the operation will be cancelled
- Features: support version control and CAS algorithm (compare and exchange)
1.3 table lock and row lock
- Table lock definition: when a piece of data is operated, the whole table including the data will be locked (no deadlock will occur)
- Definition of row lock: lock only the data to be operated (deadlock may occur because I wait for you and you wait for me)
2. General
- Read lock: shared lock, deadlock will occur
- Read lock and life and death lock mechanism: for example, threads 1 and 2 are reading a piece of data. At this time, if thread 1 still wants to perform modification, it must wait until thread 2 reads it. Similarly, if thread 2 wants to perform modification, it must wait for thread 1 to read it. That is, waiting for each other leads to deadlock
- Write lock: exclusive lock, deadlock will occur
- Write lock and life and death lock mechanism: for example, threads 1 and 2 write to a piece of data 1 and data 2 respectively. At this time, thread 1 can also write to data 2 at the same time, so it has to wait for thread 2 to complete the operation. Similarly, thread 2 can also write to data 1 at the same time, so it has to wait for thread 1 to complete the operation
3. Case demonstration (cache access)
- Before locking: I found that I started reading before I finished writing
class myCache{ private volatile Map<String,Object> map = new HashMap<>(); public void put(String key, Object value){ try { System.out.println(Thread.currentThread().getName() + "Writing" + key); TimeUnit.SECONDS.sleep(3); map.put(key,value); System.out.println(Thread.currentThread().getName() + "Finished" + key); }catch (Exception e){ e.printStackTrace(); } } public Object get(String key){ System.out.println(Thread.currentThread().getName() + "Reading" + key); try { TimeUnit.SECONDS.sleep(3); }catch (Exception e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "Finished reading" + key); return map.get(key); } } //give the result as follows 0 Writing 0 2 Writing 2 1 Writing 1 0 Reading 0 1 Reading 1 2 Reading 2 2 Finished reading 2 2 Finished 2 1 Finished reading 1 0 Finished reading 0 0 Finished writing 0 1 Finished 1
- After locking: the write operation is an exclusive lock. The next step can only be carried out after the result is written, and the conditions are met; The read operation is a shared lock. The result shows that multiple threads read together, which is also qualified
class myCache{ private volatile Map<String,Object> map = new HashMap<>(); private ReadWriteLock lock = new ReentrantReadWriteLock(); public void put(String key, Object value){ lock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "Writing" + key); TimeUnit.SECONDS.sleep(3); map.put(key,value); System.out.println(Thread.currentThread().getName() + "Finished" + key); }catch (Exception e){ e.printStackTrace(); }finally { lock.writeLock().unlock(); } } public Object get(String key){ lock.readLock().lock(); Object o = null; try { System.out.println(Thread.currentThread().getName() + "Reading" + key); TimeUnit.SECONDS.sleep(3); o = map.get(key); System.out.println(Thread.currentThread().getName() + "Finished reading" + key); }catch (Exception e){ e.printStackTrace(); }finally { lock.readLock().unlock(); } return o; } } //give the result as follows 0 Writing 0 0 Finished writing 0 2 Writing 2 2 Finished 2 1 Writing 1 1 Finished 1 0 Reading 0 1 Reading 1 2 Reading 2 0 Finished reading 0 2 Finished reading 2 1 Finished reading 1
4. Disadvantages
- Cause lock hunger, keep reading and no writing operation. For example, when you take the subway, suddenly 50 people get on the bus and only you get off the bus, you will be crowded on the bus, that is, only get on the bus and don't get off the bus
- You can't write when reading. You can write only after reading; For the same thread, while obtaining the write lock, it can also obtain the read lock, that is, it can be read under the write operation
5. Lock degradation
- Definition: the process of acquiring a read lock while occupying the current write lock, and then releasing the previously occupied write lock
- Usage scenario: in the case of multithreading, query the newly updated data immediately after updating the data
Reason: since the read lock is still held after the data is updated and the write lock is released, all threads must wait for the read lock to be released in order to obtain the write lock. At this time, the thread holding the read lock can find the newly updated data - Essence: the essence of lock degradation is to release the exclusive lock (write lock), so that other threads can obtain the read lock and improve concurrency, while the current thread holds the read lock to ensure the visibility of data
- Test cases are as follows
public class test { private int i = 0; private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private Lock writerLock = readWriteLock.writeLock(); private Lock readLock = readWriteLock.writeLock(); public void doSomething() { writerLock.lock(); try { i++; readLock.lock(); } finally { writerLock.unlock(); } try { // Simulate other complex operations Thread.sleep(500L); } catch (InterruptedException e) { e.printStackTrace(); } try { if (i == 1) { System.out.println("i The value of is" + i); } else { System.out.println("i = " + i); } } finally { readLock.unlock(); } } public static void main(String[] args) { test test = new test(); for (int i = 0; i < 5; i++) { new Thread(() -> { test.doSomething(); }).start(); } } } //result i The value of is 1 i = 2 i = 3 i = 4 i = 5