Multithreading --- explain in detail the use of various locks and locks

1. synchronized

Refer to the article below for details

https://blog.csdn.net/A_Java_Dog/article/details/118679431

  • synchronized(Object)

    Object cannot be string, integer or long, because strings are ultimately based on the data in a string constant pool.

  • Thread synchronization

    synchronized

    • The lock is the object, not the code

    • synchronized void methodName locks this (the current object)
      The synchronized static void m static method locks XXX class

      public class T {
      
          private int count = 10;
      
          public synchronized void m() { //Equivalent to synchronized(this) when the method's code executes
              count--;
              System.out.println(Thread.currentThread().getName() + " count = " + count);
          }
      }
      
      private static int count = 10;
      
      //Because this m is a static method, it is equivalent to synchronized(T.class)
      public synchronized static void m() { 
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
      
    • Locked and unlocked methods are executed synchronously

        public static void main(String[] args) {
            T t = new T();
            //m1 is a synchronous method and m2 is an asynchronous method. If m1 and m2 cannot be called at the same time, they will be executed after m1 is completed,
            //That is, m2 is executed only after m1 end is output
            //The results show that after m1 starts execution, m2 starts execution immediately, that is, the execution of both is not affected
            new Thread(t::m1, "t1").start();
            new Thread(t::m2, "t2").start();
        }
      
        public synchronized void m1() {
            System.out.println(Thread.currentThread().getName() + " m1 start...");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " m1 end");
        }
      
        public void m2() {
            System.out.println(Thread.currentThread().getName() + " m2 start...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " m2 end");
        }
      
    • Lock upgrade - bias lock, spin lock, heavyweight lock

    • Service conditions: 1 The execution time is short (locking code), the number of threads is small, and spin is used

      ​ 2. The execution time is long and the number of threads is large. The heavyweight lock (system lock) is used (the system lock has a waiting queue and does not occupy CPU resources)

      ​ 3. Long operation time - > use heavyweight lock

Reentrant lock, unfair lock, pessimistic lock

Reentrant

Same thread

In the synchronized code, the same thread can call other synchronized modified methods again, as shown in the following example

public class T01_ReentrantLock1 {
   synchronized void m1() {
      for(int i=0; i<10; i++) {
         try {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(i);
         if(i == 2) {
             //If the synchronized lock is not reentrant, it will be blocked here because the m1 method executed by the current thread holds the lock of the object,
            this.m2();
             //The result of m2 is output here to prove that the synchronized lock can be re entered under the same thread
         }
      }
      
   }
   
   synchronized void m2() {
      System.out.println("m2 ...");
   }
   
   public static void main(String[] args) {
      T01_ReentrantLock1 rl = new T01_ReentrantLock1();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

results of enforcement

Different threads

/**
 * In this example, since m1 locks this, m2 can execute only after m1 is executed
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.TimeUnit;

public class T01_ReentrantLock1 {
   synchronized void m1() {
      for(int i=0; i<10; i++) {
         try {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(i);
      }
      
   }
   
   synchronized void m2() {
      System.out.println("m2 ...");
   }
   
   public static void main(String[] args) {
      T01_ReentrantLock1 rl = new T01_ReentrantLock1();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
       //Since m1 locks this, m2 can execute only after m1 is executed
      new Thread(rl::m2).start();
   }
}

results of enforcement

2. volatile

Ensure thread visibility

  • MESI
  • Cache consistency protocol

See the three characteristics of threads above - visibility resolution

Prohibit instruction reordering

Use the following singleton mode, lazy double check as an example

package com.cyc.design.singleton;

/**
 * @author chenyunchang
 * @version 1.0
 * lazy loading
 * Also known as lazy style
 * Although it achieves the purpose of on-demand initialization, it brings the problem of thread insecurity
 * It can be solved by synchronized, but it also leads to a decrease in efficiency
 */
public class Singleton06 {

    //volatile keywordprevents instruction rearrangement
    private static volatile Singleton06 INSTANCE;


    public void c() {
        System.out.println("C");
    }

    /**
     * The constructor is private. It can only be new in the current class, and the external class cannot be new
     */
    private Singleton06() {
    }

    public static Singleton06 getInstance() {
        if (INSTANCE == null) {
            //duplication check
            synchronized (Singleton06.class) {
                if (INSTANCE == null) {
                    try {
                        //Here, let the thread entering this code block sleep for one millisecond
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton06();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() ->
                    System.out.println(Singleton06.getInstance().hashCode())
            ).start();
        }
    }

}

View results

Why add volatile?

To prevent instruction rearrangement. This involves the loading process of classes.

First, the first thread enters. After locking, it enters into INSTANCE = new Singleton06(); In the code, in the middle of initialization, that is, in the preparation stage, the memory has been applied for Singleton06, and the member variables in Singleton06 have been assigned default values, such as 0. At this time, the INSTANCE has pointed to the allocated memory and is no longer null. At this time, another thread comes in. Because the INSTANCE has been semi initialized at this time, Therefore, when if (INSTANCE == null) is false, another thread will get the member variable in this INSTANCE for operation, which obviously does not meet the requirements.

To resolve this problem, you need to view its bytecode file

For example, the following test class T uses the idea plug-in to view its bytecode file

At 0 new #2 < COM / cyc / JVM / C0_ Memory has been requested after basic / T >.

4 invokespecial #3 <com/cyc/jvm/c0_ Basic / T. > this class assigns initial values to static variables in the class

After calling 4, this memory will be assigned to t, but because the instructions may be rearranged, if 7 astore is executed first_ 1. It is equivalent to first throwing this address into memory and then performing t initialization. In this case, in the double check lazy singleton, other threads will read the semi initialized singleton.

  • Single case of DCL
  • Double Check Lock

3. Lock optimization

Lock refinement

 /**
 * synchronized optimization
 * The fewer statements in the synchronized code block, the better
 * Compare m1 and m2
 * @author cyc
 */
package com.cyc.juc.c_016_LockOptimization;
import java.util.concurrent.TimeUnit;

public class FineCoarseLock {
   
   int count = 0;

   synchronized void m1() {
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //In the business logic, only the following sentence needs sync. At this time, the whole method should not be locked
      count ++;
      
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   
   void m2() {
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //In the business logic, only the following sentence needs sync. At this time, the whole method should not be locked
      //Using fine-grained locks can shorten thread contention time and improve efficiency
      synchronized(this) {
         count ++;
      }
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   

}

Lock coarsening

When many code blocks in a method are locked, it is better to directly add the lock to the method

Lock object

/**
 * Lock an object o. if the attribute of o changes, the use of the lock will not be affected
 * However, if o becomes another object, the locked object changes
 * You should avoid turning references to locked objects into other objects or making objects of type final
 * @author cyc
 */
package com.cyc.juc.c_017_MoreAboutSync;

import java.util.concurrent.TimeUnit;


public class SyncSameObject {

   /*final*/ Object o = new Object();

   void m() {
      synchronized(o) {
         while(true) {
            try {
               TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
            
            
         }
      }
   }
   
   public static void main(String[] args) {
      SyncSameObject t = new SyncSameObject();
      //Start the first thread
      new Thread(t::m, "t1").start();
      
      try {
         TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //Create a second thread
      Thread t2 = new Thread(t::m, "t2");
      
      t.o = new Object(); //The lock object changes, so t2 thread can execute. If this sentence is commented out, thread 2 will never get an execution opportunity
      
      t2.start();
      
   }

   

}

4. CAS (lock free optimization, spin)

In Java util. Under the concurrent package, Atomic starts with classes that ensure thread safety through CAS

https://blog.csdn.net/A_Java_Dog/article/details/118679431

CAS (expected value, updated value)

m=0

m++

expected = read m;

cas(0,1){
for(; 😉// If current M value = = 0 m=1

}

5. JUC synchronous lock

1. Reentrantlock

ReentrantLock belongs to the type of CAS provided by JUC and needs to be unlocked manually. The lock must be written in try finally to ensure that it can be unlocked in the end. synchronized belongs to automatic locking and automatic release.

synchronized code block bytecode display:

ReentrantLock code block bytecode display:

Same thread

/**
 * reentrantlock Used in place of synchronized
 * Since m1 locks this, m2 can execute only after m1 is executed
 * You can do the same with reentrantlock
 * It should be noted that the lock must be released manually (say important things three times)
 * If syn locking is used, the jvm will automatically release the lock if an exception is encountered, but the lock must be released manually, so the lock is often released in finally
 * @author cyc
 */
public class T02_ReentrantLock2 {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock(); //synchronized(this)
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);

                System.out.println(i);
                if (i == 2) {
                   this.m2();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    void m2() {
        try {
            lock.lock();
            System.out.println("m2 ...");
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        T02_ReentrantLock2 rl = new T02_ReentrantLock2();
        new Thread(rl::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Different threads

public class T02_ReentrantLock2 {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock(); //synchronized(this)
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    void m2() {
        try {
            lock.lock();
            System.out.println("m2 ...");
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        T02_ReentrantLock2 rl = new T02_ReentrantLock2();
        new Thread(rl::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(rl::m2).start();
    }
}

tryLock

m1 executes for 10 seconds. During m1 execution, m2 attempts to obtain the lock in 5 seconds. If it cannot be obtained, continue to execute the following method and output m2... false.

/**
 * Use reentrantlock to "try to lock" tryLock. In this way, it cannot be locked, or it cannot be locked within a specified time. The thread can decide whether to continue to wait
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T03_ReentrantLock3 {
   Lock lock = new ReentrantLock();

   void m1() {
      try {
         lock.lock();
         for (int i = 0; i < 10; i++) {
            TimeUnit.SECONDS.sleep(1);

            System.out.println(i);
         }
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }

   /**
    * Try locking with tryLock, and the method will continue to execute regardless of whether it is locked or not
    * You can determine whether to lock according to the return value of tryLock
    * You can also specify the time of tryLock. Since tryLock(time) throws an exception, you should pay attention to the processing of unclock and put it in finally
    */
   void m2() {
      /*
      boolean locked = lock.tryLock();
      System.out.println("m2 ..." + locked);
      if(locked) lock.unlock();
      */
      
      boolean locked = false;
      
      try {
         // Try to acquire the lock within 5 seconds. If it cannot be acquired, give up to acquire it
         locked = lock.tryLock(5, TimeUnit.SECONDS);
         System.out.println("m2 ..." + locked);
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         if(locked) lock.unlock();
      }
      
   }

   public static void main(String[] args) {
      T03_ReentrantLock3 rl = new T03_ReentrantLock3();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      new Thread(rl::m2).start();
   }
}

results of enforcement

Change the m1 method to sleep for 3 seconds,

   for (int i = 0; i < 3; i++) {
            TimeUnit.SECONDS.sleep(1);

            System.out.println(i);
         }

Output result m2... true

lockInterruptibly()

/**
 * Using ReentrantLock, you can also call the lockinterruptible method to respond to the thread interrupt method,
 * A thread can be interrupted while waiting for a lock
 * @author cyc
 */
public class T04_ReentrantLock4 {
		
	public static void main(String[] args) {
		Lock lock = new ReentrantLock();
		
		
		Thread t1 = new Thread(()->{
			try {
				lock.lock();
				System.out.println("t1 start");
				TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
				System.out.println("t1 end");
			} catch (InterruptedException e) {
				System.out.println("interrupted!");
			} finally {
				lock.unlock();
			}
		});
		t1.start();
		
		Thread t2 = new Thread(()->{
			try {
				//lock.lock();
				lock.lockInterruptibly(); //You can respond to the interrupt() method
				System.out.println("t2 start");
				TimeUnit.SECONDS.sleep(5);
				System.out.println("t2 end");
			} catch (InterruptedException e) {
				System.out.println("t2 interrupted!");
			} finally {
				lock.unlock();
			}
		});
		t2.start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.interrupt(); //Interrupt thread 2 waiting
		
	}
}

  • Output results

Fair lock

new ReentrantLock(true) is to create a new fair lock. The so-called fair lock means that before each thread obtains the lock, it must go to the lock waiting queue to check whether there are threads waiting for the lock. If so, it will join the waiting queue and obtain the lock in order. If not, go straight to get the lock.

new ReentrantLock() is a non fair lock by default. It ignores the waiting queue and directly competes for the lock.

Note: synchronized is a non fair lock

/**
 *
 * ReentrantLock It can also be specified as a fair lock
 *
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.locks.ReentrantLock;

public class T05_ReentrantLock5 extends Thread {

    private static ReentrantLock lock = new ReentrantLock(true); //If the parameter is true, it means fair lock (false by default). Please compare the output results

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "Acquire lock");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        T05_ReentrantLock5 rl = new T05_ReentrantLock5();
        Thread th1 = new Thread(rl);
        Thread th2 = new Thread(rl);
        th1.start();
        th2.start();
    }
}
  • Output results

2. Readwritelock

/**
 * Read write lock (shared lock + exclusive lock). When reading, it is shared and when writing, it is exclusive
 * @author fei
 */
public class T10_TestReadWriteLock {
    static Lock lock = new ReentrantLock();
    private static int value;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read over!");
            //Analog read operation
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
            //Analog write operation
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }



    public static void main(String[] args) {
        //Exclusive lock
//        Runnable readR = ()-> read(lock);
        //Read / write lock - read. With read / write lock, multiple threads can read the same data at the same time without waiting
        Runnable readR = ()-> read(readLock);

//        Runnable writeR = ()->write(lock, new Random().nextInt());
        //Read / write lock - write. When using the read / write lock, other threads need to wait for the thread to complete before reading and writing
        Runnable writeR = ()->write(writeLock, new Random().nextInt());

        for(int i=0; i<18; i++) new Thread(readR).start();
        for(int i=0; i<2; i++) new Thread(writeR).start();


    }
}

3. Langadder (section lock)

/**
 * Test of execution efficiency of several locks
 * count1,count2, count3 Different ways are used to realize increment
 */
public class T02_AtomicVsSyncVsLongAdder {
    static long count2 = 0L;
    static AtomicLong count1 = new AtomicLong(0L);
    //LongAdder performs an operation similar to segment lock (segment lock is also a CAS operation). For example, in this example, 1000 threads may be divided into multiple groups,
    // Each group is calculated separately, and the calculated values are finally accumulated to obtain the total value. LongAdder has obvious advantages when there are many threads
    static LongAdder count3 = new LongAdder();

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[1000];

        for(int i=0; i<threads.length; i++) {
            threads[i] =
                    new Thread(()-> {
                        for(int k=0; k<100000; k++) {
                            count1.incrementAndGet();
                        }
                    });
        }

        long start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        long end = System.currentTimeMillis();

        //TimeUnit.SECONDS.sleep(10);

        System.out.println("Atomic: " + count1.get() + " time " + (end-start));
        //-----------------------------------------------------------
        Object lock = new Object();

        for(int i=0; i<threads.length; i++) {
            threads[i] =
                new Thread(() -> {
                    for (int k = 0; k < 100000; k++) {
                        synchronized (lock) {
                            count2++;
                        }
                    }
                });
        }

        start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        end = System.currentTimeMillis();


        System.out.println("Sync: " + count2 + " time " + (end-start));


        //----------------------------------
        for(int i=0; i<threads.length; i++) {
            threads[i] =
                    new Thread(()-> {
                        for(int k=0; k<100000; k++) {
                            count3.increment();
                        }
                    });
        }

        start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        end = System.currentTimeMillis();

        //TimeUnit.SECONDS.sleep(10);

        System.out.println("LongAdder: " + count1.longValue() + " time " + (end-start));
    }
}

4. Countdownlatch

Countdown bolt

/**
 * CountDown Countdown Latch countdown Latch open the Latch
 * It is often used on the control thread. You can control the execution of the following code after the execution of the thread
 *
 * @author cyc
 * @date 2021-08-07 16:03:42
 */
public class T06_TestCountDownLatch {
    public static void main(String[] args) {
        usingJoin();
        //Using CountDownLatch is more flexible than join ing
        usingCountDownLatch();
    }

    private static void usingCountDownLatch() {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = new CountDownLatch(threads.length);

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                //The following threads [i] After start() is executed, the following code is executed,
                //Each time, latch Countdown() will be reduced by 1 accordingly. When it is reduced to 0,
                // latch.await()
                int result = 0;
                for(int j=0; j<10000; j++) {
                    result += j;
                }
                latch.countDown();
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        try {
            System.out.println("latch await Waiting for execution");
            //The main thread will be blocked here. The following code will continue to execute only when the latch is reduced to 0
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("end latch");
    }

    private static void usingJoin() {
        Thread[] threads = new Thread[100];

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                int result = 0;
                for(int j=0; j<10000; j++) {
                    result += j;
                }
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        for (int i = 0; i < threads.length; i++) {
            try {
                //Each thread is merged into the main thread. The main thread has to wait until all threads finish executing,
                // Just continue to execute the following code (prepare to start, and leave when full)
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("end join");
    }
}

  • results of enforcement

5. Cyclicbarrier

Circular fence

package com.cyc.juc.c_020_lock;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Cyclic Cyclic Barrier barrier
 * @author cyc
 * @date 2021-08-07 16:35:30
 */
public class T07_TestCyclicBarrier {
    public static void main(String[] args) {
        //CyclicBarrier barrier = new CyclicBarrier(20);	

        CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("Manchu"));

        for(int i=0; i<100; i++) {

                new Thread(()->{
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
            
        }
    }
}
  • results of enforcement

  • Observation source code


Click barrier The await () method finds that when the number of threads executed reaches n in the new CyclicBarrier(n), a new loop fence is regenerated. According to the observation results, the value of n is 20 and the number of threads executed is 100, so the execution results of the run method in the five cyclicbarriers are output

Application scenario

  • Complex operations and concurrent execution

    For example, there are three threads a, B and C, which respectively access the database, access the network and read files. At this time, CyclicBarrier can be used. It can execute the main process only after all threads have been executed

6. The difference between countdownlatch and CyclicBarrier

CountDownLatch is to set a value below countdown each time until it is equal to 0. However, it is not necessary to count down in each thread. Countdown in one thread is also OK

For example, it can still be written like this

        Thread t = new Thread(() -> {
            for (int i = 0; i < threads.length; i++) {
                latch.countDown();
            }
        });
        t.start();

CyclicBarrier is different. It must call await() method in each thread

Test it

for (int i = 0; i < 100; i++) {
    try {
        System.out.println("Waiting");
        barrier.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
}

It will only output once, waiting, and then it will be blocked there.

7. Semaphore

It is often used in current limiting,

For example, lane and toll station. There are only two toll stations, and there may be multiple lanes

package com.cyc.juc.c_020_lock;

import java.util.concurrent.Semaphore;

/**
 * Semaphore Semaphore(n) allows up to n threads to run simultaneously
 * Commonly used in current limiting
 * @author fei
 */
public class T11_TestSemaphore {
    public static void main(String[] args) {
        //Two threads are allowed to execute at the same time without sequence
        Semaphore s = new Semaphore(2);
        //Two threads are allowed to execute at the same time. There is a fair lock. There is an internal waiting queue and it is executed in sequence
//        Semaphore s = new Semaphore(2, true);
        //Allow one thread to execute simultaneously
        //Semaphore s = new Semaphore(1);

        new Thread(()->{
            try {
                //Blocking method. If it is not available, the blocking is here
                //Take it here, and the 1 in new Semaphore(1) will become 0
                s.acquire();

                System.out.println("T1 running...");
                Thread.sleep(200);
                System.out.println("T1 running...");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //Add back 1, that is, add back the thread
                s.release();
            }
        }).start();

        new Thread(()->{
            try {
                s.acquire();

                System.out.println("T2 running...");
                Thread.sleep(200);
                System.out.println("T2 running...");

                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

8. LockSupport

/**
 * LockSupport You can wake up the specified thread. unPark can be called before park.
 */
public class T13_TestLockSupport {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if(i == 5) {
                    //Blocking the current thread requires manual wake-up
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();

        try {
            TimeUnit.SECONDS.sleep(8);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after 8 senconds!");

        LockSupport.unpark(t);
    }
}

  • results of enforcement

6. Test questions

Thread synchronization

Implement a container and provide two methods, add,size
 Write two threads. Thread 1 adds 10 elements to the container. Thread 2 monitors the number of elements. When the number reaches 5, thread 2 gives a prompt and ends

Method 1 general judgment

Thread out of sync

public class T01_WithoutVolatile {

	List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}
	
	public static void main(String[] args) {
		T01_WithoutVolatile c = new T01_WithoutVolatile();

		new Thread(() -> {
			for(int i=0; i<10; i++) {
				c.add(new Object());
				System.out.println("add " + i);
				
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t1").start();
		
		new Thread(() -> {
			while(true) {
				if(c.size() == 5) {
					break;
				}
			}
			System.out.println("t2 end");
		}, "t2").start();
	}
}

  • Output results
add 0
add 1
add 2
add 3
add 4
add 5
add 6
add 7
add 8
add 9

Because the value added by thread 1 to the list is different and is synchronized to thread 2 immediately, thread 2 cannot end

Using thread synchronization

/**
 * After adding volatile to lists, t2 can be notified. However, the dead loop of t2 thread is a waste of cpu. If you don't use the dead loop,
 * Moreover, if it is interrupted by another thread between if and break, the result is not accurate,
 * @author cyc
 */
public class T02_WithVolatile {

   //Add volatile to enable t2 to be notified
   //volatile List lists = new LinkedList();
   //Synchronization container
   volatile List lists = Collections.synchronizedList(new LinkedList<>());

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }

   public static void main(String[] args) {

      T02_WithVolatile c = new T02_WithVolatile();
      new Thread(() -> {
         for(int i=0; i<10; i++) {
            c.add(new Object());
            System.out.println("add " + i);
  
            try {
               TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }, "t1").start();
      
      new Thread(() -> {
         while(true) {
            if(c.size() == 5) {
               break;
            }
         }
         System.out.println("t2 end");
      }, "t2").start();
   }
}
  • Output results

add 0
add 1
add 2
add 3
add 4
t2 end
add 5
add 6
add 7
add 8
add 9

Method 2: wait() and notify()

Counterexample

/**
 * Here, wait and notify are used. Wait will release the lock, while notify will not release the lock
 * It should be noted that when using this method, t2 must be executed first, that is, t2 must be monitored first
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class T03_NotifyHoldingLock { //wait notify

   //Add volatile to enable t2 to be notified
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }
   
   public static void main(String[] args) {
      T03_NotifyHoldingLock c = new T03_NotifyHoldingLock();
      
      final Object lock = new Object();
      
      new Thread(() -> {
         synchronized(lock) {
            System.out.println("t2 start-up");
            if(c.size() != 5) {
               try {
                  lock.wait();
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
            System.out.println("t2 end");
         }
         
      }, "t2").start();
      
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      new Thread(() -> {
         System.out.println("t1 start-up");
         synchronized(lock) {
            for(int i=0; i<10; i++) {
               c.add(new Object());
               System.out.println("add " + i);
               
               if(c.size() == 5) {
                  //notify() does not release the lock, so t1 will wait for t2 to execute
                  lock.notify();
               }
               
               try {
                  TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         }
      }, "t1").start();
       
   }
}

optimization method

/**
 * notify After that, t1 must release the lock. After t2 exits, it must notify to inform t1 to continue execution
 * The whole communication process is cumbersome
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class T04_NotifyFreeLock {

   //Add volatile to enable t2 to be notified
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }
   
   public static void main(String[] args) {
      T04_NotifyFreeLock c = new T04_NotifyFreeLock();
      
      final Object lock = new Object();
      
      new Thread(() -> {
         synchronized(lock) {
            System.out.println("t2 start-up");
            if(c.size() != 5) {
               try {
                  lock.wait();
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
            System.out.println("t2 end");
            //Notify t1 to continue
            lock.notify();
         }
         
      }, "t2").start();
      
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      Thread t = new Thread(() -> {
         System.out.println("test");
      });

      new Thread(() -> {
         System.out.println("t1 start-up");
         synchronized(lock) {
            for(int i=0; i<10; i++) {
               c.add(new Object());
               System.out.println("add " + i);
               
               if(c.size() == 5) {
                  lock.notify();
                  //Release the lock to allow t2 to execute
                  try {
                     lock.wait();
                  } catch (InterruptedException e) {
                     e.printStackTrace();
                  }
               }
               
               try {
                  TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         }
      }, "t1").start();
      
      
   }
}

Method 3 CountDownLatch()

A countDownLatch

/**
 * @author cyc
 */
public class T05_CountDownLatch {

   // Add volatile to enable t2 to be notified
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }

   public static void main(String[] args) {
      T05_CountDownLatch c = new T05_CountDownLatch();

      CountDownLatch latch = new CountDownLatch(1);

      new Thread(() -> {
         System.out.println("t2 start-up");
         if (c.size() != 5) {
            try {
               latch.await();
               
               //You can also specify the waiting time
               //latch.await(5000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
         System.out.println("t2 end");

      }, "t2").start();

      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      new Thread(() -> {
         System.out.println("t1 start-up");
         for (int i = 0; i < 10; i++) {
            c.add(new Object());
            System.out.println("add " + i);

            if (c.size() == 5) {
               // Open the latch to allow t2 to execute
               latch.countDown();
            }
         }

      }, "t1").start();

   }
}

results of enforcement

t2 start
t1 start
add 0
add 1
add 2
add 3
add 4
add 5
add 6
t2 end
add 7
add 8
add 9

It can be seen that normally, t2 printing should end after printing add 4, but due to the latch of t1 await(); After execution and before the end of printing t2, the thread of t1 executes twice, resulting in "t2 technology" output only when i=6.

Optimize CountDownLatch

Use two CountDownLatch

/**
 * countDownLatch optimization
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class T05_CountDownLatch_optimize {

	// Add volatile to enable t2 to be notified
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	public static void main(String[] args) {
		T05_CountDownLatch_optimize c = new T05_CountDownLatch_optimize();

		CountDownLatch latch1 = new CountDownLatch(1);
		CountDownLatch latch2 = new CountDownLatch(1);

		new Thread(() -> {
			System.out.println("t2 start-up");
			if (c.size() != 5) {
				try {
					latch1.await();
					//You can also specify the waiting time
					//latch1.await(5000, TimeUnit.MILLISECONDS);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("t2 end");
            //After t2 execution, release the door bolt
			latch2.countDown();

		}, "t2").start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

		new Thread(() -> {
			System.out.println("t1 start-up");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					// Open the latch to allow t2 to execute
					latch1.countDown();
					try {
                        //latch1.countDown(); After execution, continue to execute latch2 await(); Wait for latch 2 to return to zero
						latch2.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				/*try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}*/
			}

		}, "t1").start();

	}
}

Method 4 LockSupport

Using lockSupport and CountDownLatch above will have the same problem. Let's start the demonstration

General scheme

/**
 * @author cyc
 */
public class T06_LockSupport {

	// Add volatile to enable t2 to be notified
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	public static void main(String[] args) {
		T06_LockSupport c = new T06_LockSupport();

		CountDownLatch latch = new CountDownLatch(1);

		Thread t2 = new Thread(() -> {
			System.out.println("t2 start-up");
			if (c.size() != 5) {

				LockSupport.park();

			}
			System.out.println("t2 end");


		}, "t2");

		t2.start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

		new Thread(() -> {
			System.out.println("t1 start-up");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					LockSupport.unpark(t2);
				}
			}

		}, "t1").start();

	}
}

Similarly, the output statement ending in t2 may not be after t1 outputs add 4, but may be after add5, add6

Optimization scheme LockSupport

/**
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

public class T07_LockSupport_WithoutSleep {

	// Add volatile to enable t2 to be notified
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	static Thread t1 = null, t2 = null;

	public static void main(String[] args) {
		T07_LockSupport_WithoutSleep c = new T07_LockSupport_WithoutSleep();

		t1 = new Thread(() -> {
			System.out.println("t1 start-up");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					LockSupport.unpark(t2);
					LockSupport.park();
				}
			}
		}, "t1");

		t2 = new Thread(() -> {
			System.out.println("t2 start-up");
			LockSupport.park();
			System.out.println("t2 end");
			LockSupport.unpark(t1);


		}, "t2");

		t2.start();
		t1.start();

	}
}

Method 5 Semaphore

public class T08_Semaphore {
    // Add volatile to enable t2 to be notified
    volatile List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    static Thread t1 = null, t2 = null;

    public static void main(String[] args) {
        T08_Semaphore c = new T08_Semaphore();
        Semaphore s = new Semaphore(1);

        t1 = new Thread(() -> {
            try {
                s.acquire();
                for (int i = 0; i < 5; i++) {
                    c.add(new Object());
                    System.out.println("add " + i);
                }
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                t2.start();
                //After t2 is executed, t1 continues to execute
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                s.acquire();
                for (int i = 5; i < 10; i++) {
                    System.out.println("add " + i);
                }
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "t1");

        t2 = new Thread(() -> {
            try {
                s.acquire();
                System.out.println("t2 end");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        t1.start();
    }
}

Container - producer consumer

* Interview question: write a fixed capacity synchronous container with put and get Methods, and getCount method,
* It can support blocking calls of 2 producer threads and 10 consumer threads

1. Use wait and notify/notifyAll to implement

/**
 * Write a fixed capacity synchronization container with put and get methods and getCount methods,
 * It can support blocking calls of 2 producer threads and 10 consumer threads
 * 
 * It is implemented using wait and notify/notifyAll
 * 
 * @author cyc
 */
public class MyContainer1<T> {
   final private LinkedList<T> lists = new LinkedList<>();
   final private int MAX = 10; //Up to 10 elements
   private int count = 0;


   /**
    * production
    * @param t
    */
   public synchronized void put(T t) {
      while(lists.size() == MAX) { //Think about why you use while instead of if?, If you use if here,
         // Finish this After wait(), you will not continue to judge lists Size() = = max, but go down.
         try {
            this.wait(); //effective java
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }

      lists.add(t);
      ++count;
      this.notifyAll(); //Notify the consumer thread to consume
   }

   /**
    * consumption
    * @return
    */
   public synchronized T get() {
      T t = null;
      while(lists.size() == 0) {
         try {
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      t = lists.removeFirst();
      count --;
      this.notifyAll(); //Notify the producer for production
      return t;
   }

   public static void main(String[] args) {
      MyContainer1<String> c = new MyContainer1<>();
      //Start consumer thread
      for(int i=0; i<10; i++) {
         new Thread(()->{
            for(int j=0; j<5; j++) {
               System.out.println(c.get()+", count: "+ c.count);
            }
         }, "c" + i).start();
      }

      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      //Start producer thread
      for(int i=0; i<2; i++) {
         new Thread(()->{
            for(int j=0; j<25; j++) {
               c.put(Thread.currentThread().getName() + " " + j);
            }
         }, "p" + i).start();
      }
   }
}

Output results

p1 0, count: 9
p1 5, count: 4
p1 6, count: 3
p1 7, count: 2
p1 8, count: 1
p1 4, count: 10
p1 9, count: 9
p0 0, count: 8
p0 1, count: 7

...

2. Use lock newCondition()

There is a problem with the above code, that is, when notifyAll is used, it will notify all threads, not just the consumer threads, and then use lock newCondition() creates a new condition, which is the waiting queue. Each notification only notifies the thread corresponding to the waiting queue

/**
 * Write a fixed capacity synchronization container with put and get methods and getCount methods,
 * It can support blocking calls of 2 producer threads and 10 consumer threads
 * Using Lock and Condition
 * Compared with the two methods, the Condition method can more accurately specify which threads are awakened
 * 
 * @author cyc
 */
package com.cyc.juc.c_021_01_interview;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyContainer2<T> {
	final private LinkedList<T> lists = new LinkedList<>();
	final private int MAX = 10; //Up to 10 elements
	private int count = 0;
	
	private Lock lock = new ReentrantLock();
	//Condition is essentially a waiting queue. new Condition() is equivalent to creating a new waiting queue
	private Condition producer = lock.newCondition();
	private Condition consumer = lock.newCondition();
	
	public void put(T t) {
		try {
			lock.lock();
			while(lists.size() == MAX) { //Think about why you use while instead of if?
				producer.await();
			}
			
			lists.add(t);
			++count;
			consumer.signalAll(); //Notify the consumer thread to consume
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public T get() {
		T t = null;
		try {
			lock.lock();
			while(lists.size() == 0) {
				consumer.await();
			}
			t = lists.removeFirst();
			count --;
			producer.signalAll(); //Notify the producer for production
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return t;
	}
	
	public static void main(String[] args) {
		MyContainer2<String> c = new MyContainer2<>();
		//Start consumer thread
		for(int i=0; i<10; i++) {
			new Thread(()->{
				for(int j=0; j<5; j++) System.out.println(c.get());
			}, "c" + i).start();
		}
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//Start producer thread
		for(int i=0; i<2; i++) {
			new Thread(()->{
				for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
			}, "p" + i).start();
		}
	}
}

Keywords: Multithreading Concurrent Programming

Added by --ss-- on Mon, 27 Dec 2021 09:23:35 +0200