[crazy God says Java] JUC concurrent programming

Posted by mark bowen on Wed, 08 Dec 2021 03:39:44 +0100

[crazy God says Java] JUC concurrent programming

preparation

Create a Maven project and introduce a lombok dependency

 <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>

1. What is JUC

JUC is the class package under java.util.concurrent, which is specially used for multithreading development.

2. Threads and processes

Process is the application in the operating system and the basic unit of resource allocation. Thread is used to perform specific tasks and functions. It is the smallest unit of CPU scheduling and allocation. A process can often contain multiple threads, at least one

1) Process

A program, a collection of QQ.exe Music.exe programs;
A process can often contain multiple threads, at least one!
How many threads does Java have by default? 2 mian s, GC

2) Thread

Open a process Typora, write and save automatically (thread is responsible)

For Java, Thread, Runable and Callable are used to start threads.

put questions to? Can JAVA really start threads? I can't drive!

Java does not have permission to start threads and operate hardware. This is a native local method that calls the underlying C + + code.

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
	//This is a C + + bottom layer. Java has no permission to operate the bottom hardware
    private native void start0();


first. start() is a synchronized method, which is safe. This method will add the current thread to a thread group and call the start0() method. This start0() is modified with native, that is, the local method. So finally, the local method is called, and the JAV has no permission to start the thread. start() calls the local C + + method because java runs on the virtual machine and cannot directly operate the hardware

3) Concurrent

Multiple threads operate on the same resource.

  • The CPU has only one core and simulates multiple threads. The world's martial arts are only fast. Then we can use CPU fast alternation to simulate multithreading.
  • The essence of concurrent programming: make full use of CPU resources!

4) Parallel

Parallel: multiple people walking together

  • The CPU is multi-core, and multiple threads can execute at the same time. We can use thread pool!

Gets the number of cores of the cpu

public class Test1 {
    public static void main(String[] args) {
        //Gets the number of cores of the cpu
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

5) Status of the thread

public enum State {

    	//newborn
        NEW,

    	//function
        RUNNABLE,

    	//block
        BLOCKED,

    	//wait for
        WAITING,

    	//Timeout wait
        TIMED_WAITING,

    	//termination
        TERMINATED;
    }


6) The difference between wait/sleep

1. From different classes

wait => Object

sleep => Thread

Generally, dormancy is used in Enterprises:

TimeUnit.DAYS.sleep(1); //Dormancy for 1 day
TimeUnit.SECONDS.sleep(1); //Sleep for 1s

2. About lock release

wait will release the lock;

sleep sleeps and won't release the lock;

3. The scope of use is different

wait must be in the synchronization code block;

Sleep can sleep anywhere;

4. Need to catch exceptions

wait is an exception that does not need to be caught;

sleep must catch exceptions;

3.Lock

1) Traditional synchronized

//Basic ticket selling examples

public class SaleTicketDemo01 {

  public static void main(String[] args) {
    final Ticket ticket = new Ticket();

    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"A").start();
    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"B").start();
    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"C").start();
  }
}
// Resource class OOP attributes and methods
class Ticket {
  private int number = 30;

  //How to sell tickets
  public synchronized void sale() {
    if (number > 0) {
      System.out.println(Thread.currentThread().getName() + "Sold the third" + (number--) + "Ticket remaining" + number + "Ticket");
    }
  } 
}

2)Lock

Fair lock: very fair, first come, first served, queue up
Unfair lock: unfair, you can jump the queue
The default is a non fair lock for fairness. For example, one thread needs 3s and another thread needs 3h. Do you have to let the 3h lock execute first

public class SaleTicketDemo02  {

  public static void main(String[] args) {
    final Ticket ticket = new Ticket();

    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"A").start();
    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"B").start();
    new Thread(()->{
      for (int i = 0; i < 40; i++) {
        ticket.sale();
      }
    },"C").start();
  }
}
// Resource class OOP attributes and methods
class Ticket2 {

  private int number = 30;
  Lock lock=new ReentrantLock();
  //How to sell tickets
  public  void sale() {
    lock.lock();

    try {
      if (number > 0) {
        System.out.println(Thread.currentThread().getName() + "Sold the third" + (number--) + "Ticket remaining" + number + "Ticket");
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
}

3. The difference between synchronized and Lock

1. Synchronized built-in Java keyword, Lock is a Java class

2. Synchronized cannot judge the status of obtaining the Lock, but Lock can judge

3. Synchronized will automatically release the lock. Lock must be manually locked and manually released! Deadlock may be encountered

4. Synchronized thread 1 (get lock - > block), thread 2 (wait); Lock does not have to wait all the time. Lock will have a trylock to try to obtain the lock, which will not cause a long wait.

5. Synchronized is a reentrant lock, non interruptible and unfair; Lock, which is reentrant, can judge the lock, and can set its own fair lock and unfair lock;

6. Synchronized is suitable for locking a small number of code synchronization problems, and Lock is suitable for locking a large number of synchronization codes;

4. Relationship between producers and consumers

Interview frequency: single case mode, eight sorting, producer consumer, deadlock

1) Synchronized version

/*
Communication between threads: producer and consumer issues
 Threads alternately execute a and B operations, and the same variable num = 0, a makes num+1, and B makes num-1;
 **/
public class ConsumeAndProduct {
  public static void main(String[] args) {
    Data data = new Data();

    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.increment();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "A").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.decrement();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "B").start();
  }
}

class Data {
  private int num = 0;

  // +1
  public synchronized void increment() throws InterruptedException {
    // Judgment waiting
    if (num != 0) {
      this.wait();
    }
    num++;
    System.out.println(Thread.currentThread().getName() + "=>" + num);
    // Notify other threads that + 1 execution is complete
    this.notifyAll();
  }

  // -1
  public synchronized void decrement() throws InterruptedException {
    // Judgment waiting
    if (num == 0) {
      this.wait();
    }
    num--;
    System.out.println(Thread.currentThread().getName() + "=>" + num);
    // Notify other threads that - 1 execution is complete
    this.notifyAll();
  }
}


2) There is a problem (false wake-up)

Problem: if there are four threads, a false wake-up will occur

The solution is to change if to while to prevent false wake-up

Conclusion: if it is judged by if, the thread will start running from the code after the wait after wake-up, but will not re judge the if condition. It will directly continue to run the code after the if code block. If while is used, it will also run from the code after the wait, but the loop condition will be re judged after wake-up. If it is not true, execute the code block after the while code block, If established, continue to wait.

This is why we use while instead of if, because after the thread is awakened, the execution starts after wait

3) Lock version

public class LockCAP {
  public static void main(String[] args) {
    Data2 data = new Data2();

    new Thread(() -> {
      for (int i = 0; i < 10; i++) {

        try {
          data.increment();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

      }
    }, "A").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.decrement();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "B").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.increment();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "C").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          data.decrement();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }, "D").start();
  }
}

class Data2 {
  private int num = 0;
  Lock lock = new ReentrantLock();
  Condition condition = lock.newCondition();
  // +1
  public  void increment() throws InterruptedException {
    lock.lock();
    try {
      // Judgment waiting
      while (num != 0) {
        condition.await();
      }
      num++;
      System.out.println(Thread.currentThread().getName() + "=>" + num);
      // Notify other threads that + 1 execution is complete
      condition.signalAll();
    }finally {
      lock.unlock();
    }

  }

  // -1
  public  void decrement() throws InterruptedException {
    lock.lock();
    try {
      // Judgment waiting
      while (num == 0) {
        condition.await();
      }
      num--;
      System.out.println(Thread.currentThread().getName() + "=>" + num);
      // Notify other threads that + 1 execution is complete
      condition.signalAll();
    }finally {
      lock.unlock();
    }

  }
}


4) Advantages of Condition

Accurate notification and wake-up threads!

What if we want to specify the next order of notification? We can use Condition to specify the notification process~

Any new technology is definitely not just covering the original technology, advantages and supplements!

/**
 * Description: 
 * A Call B after execution
 * B Call C after execution
 * C Call A after execution
 *
 * @author jiaoqianjin
 * Date: 2020/8/11 9:58
 **/

public class C {
  public static void main(String[] args) {
    Data3 data3 = new Data3();

    new Thread(()  -> {
      for (int i = 0; i < 10; i++) {
        data3.printA();
      }
    },"A").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        data3.printB();
      }
    },"B").start();
    new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        data3.printC();
      }
    },"C").start();
  }

}
class Data3 {
  private Lock lock = new ReentrantLock();
  private Condition condition1 = lock.newCondition();
  private Condition condition2 = lock.newCondition();
  private Condition condition3 = lock.newCondition();
  private int num = 1; // 1A 2B 3C

  public void printA() {
    lock.lock();
    try {
      // Business code judgment - > execution - > notification
      while (num != 1) {
        condition1.await();
      }
      System.out.println(Thread.currentThread().getName() + "==> AAAA" );
      num = 2;
      condition2.signal();
    }catch (Exception e) {
      e.printStackTrace();
    }finally {
      lock.unlock();
    }
  }
  public void printB() {
    lock.lock();
    try {
      // Business code judgment - > execution - > notification
      while (num != 2) {
        condition2.await();
      }
      System.out.println(Thread.currentThread().getName() + "==> BBBB" );
      num = 3;
      condition3.signal();
    }catch (Exception e) {
      e.printStackTrace();
    }finally {
      lock.unlock();
    }
  }
  public void printC() {
    lock.lock();
    try {
      // Business code judgment - > execution - > notification
      while (num != 3) {
        condition3.await();
      }
      System.out.println(Thread.currentThread().getName() + "==> CCCC" );
      num = 1;
      condition1.signal();
    }catch (Exception e) {
      e.printStackTrace();
    }finally {
      lock.unlock();
    }
  }
}
/*
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
...
*/

5.8 lock phenomenon

How to judge who the lock is! Who is the lock?

Locks lock: object, Class

Deep understanding of our locks

Question 1

Two synchronization methods, SMS or phone

public class dome01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> { phone.sendMs(); }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> { phone.call(); }).start();
    }
}

class Phone {
    public synchronized void sendMs() {
        System.out.println("send message");
    }
    public synchronized void call() {
        System.out.println("phone");
    }
}

The output result is

send message

phone

Why? If you think it's first? This answer is wrong!

Question 2:

Let's look again: we delay texting by 4s

public class dome01 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendMs();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> { phone.call(); }).start();
    }
}

class Phone {
    public synchronized void sendMs() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send message");
    }
    public synchronized void call() {
        System.out.println("phone");
    }
}

What is the result now?

Result: send text messages first and then call again!

Reason: it is not sequential execution, but synchronized. The locked object is a method call! For the two methods, the same lock is used. Who gets the lock first, who executes it first, and the other waits

Question three

Add a common method

public class dome01 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendMs();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> { phone.hello(); }).start();
    }
}

class Phone {
    public synchronized void sendMs() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send message");
    }
    public synchronized void call() {
        System.out.println("phone");
    }
    public void hello() {
        System.out.println("hello");
    }
}

The output result is

hello

send message

Reason: hello is a common method. It is not affected by the synchronized lock and does not have to wait for the lock to be released

Question 4

If we use two objects, one calling to send a text message and the other calling to make a phone call, what is the whole order?

public class dome01 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendMs();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> { phone2.call(); }).start();
    }
}

class Phone {
    public synchronized void sendMs() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send message");
    }
    public synchronized void call() {
        System.out.println("phone");
    }
    public void hello() {
        System.out.println("hello");
    }
}

Output results

phone

send message

Reason: two objects have two locks, and there will be no waiting. I slept for 4s after texting, so I made a call first

Questions 5 and 6

If we add static to the synchronized method, it becomes a static method! So what's the order?

(1) Let's call two methods with one object!

The answer is: text first, then call

(2) If we use two objects to call two methods!

The answer is: send text messages first and then call

Why? Why is the previous object always executed first when static is added! Why wait behind?

The reason is: for static methods, there is only one copy for the whole Class. For different objects, the same method is used, which means that this method belongs to this Class. If static static methods use synchronized locking, the synchronized lock will lock the whole object! No matter how many objects, there is only one static lock. Whoever gets the lock first will execute it first, and other processes need to wait!

Question 7

If we use a static synchronization method, a synchronization method and an object, what is the calling order?

public class dome01 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
//        Phone phone2 = new Phone();

        new Thread(() -> { 
            try {
                phone1.sendMs();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> { phone1.call(); }).start();
    }
}

class Phone {
    public static synchronized void sendMs() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send message");
    }
    public synchronized void call() {
        System.out.println("phone");
    }
    public void hello() {
        System.out.println("hello");
    }
}

Output results

phone

send message

Reason: because one lock is the template of Class class and the other lock is the caller of object. So there is no waiting, run directly.

Question 8

If we use a static synchronization method, a synchronization method and two objects, what is the calling order?

public class dome01 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendMs();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> { phone2.call(); }).start();
    }
}

class Phone {
    public static synchronized void sendMs() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send message");
    }
    public synchronized void call() {
        System.out.println("phone");
    }
    public void hello() {
        System.out.println("hello");
    }
}

Output results

phone

send message

Reason: the two locks are not the same thing

Summary

this from new is a concrete object

static Class is the only template

6. Unsafe assembly

1) List is not safe

//java.util.ConcurrentModificationException concurrent modification exception!
public class ListTest {
    public static void main(String[] args) {

        List<Object> arrayList = new ArrayList<>();

        for(int i=1;i<=10;i++){
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            },String.valueOf(i)).start();
        }

    }
}

It will cause java.util.ConcurrentModificationException concurrent modification exception!

ArrayList is not safe in the case of concurrency

Solution:

public class ListTest {
    public static void main(String[] args) {
        /**
         * Solution
         * 1. List<String> list = new Vector<>();
         * 2. List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3. List<String> list = new CopyOnWriteArrayList<>();
         */
        List<String> list = new CopyOnWriteArrayList<>();
        

        for (int i = 1; i <=10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList: copy on write! An optimization strategy in the field of COW computer programming

The core idea is that if multiple Callers require the same resource (such as memory or data storage on disk) at the same time, they will jointly obtain the same pointer to the same resource. The system will not really copy a private copy to the caller until a caller's view modifies the resource content, The original resources seen by other Callers remain unchanged. This process is transparent to other Callers. The main advantage of this method is that if the caller does not modify the resource, no private copy will be created. Therefore, multiple Callers can share the same resource only during the read operation.

There is no need to lock when reading. If multiple threads are adding data to the CopyOnWriteArrayList when reading, the old data will still be read, because the old CopyOnWriteArrayList will not be locked when writing.

When multiple threads call, list, read, fixed, write (overwrite operation exists); Avoid overwriting when writing, resulting in data disorder;

CopyOnWriteArrayList is better than Vector. Where is it?

The underlying Vector is implemented using the synchronized keyword: it is particularly inefficient.

CopyOnWriteArrayList uses Lock lock, which will be more efficient!

2) set is not safe

Set and List are the same: in the case of multithreading, an ordinary set is thread unsafe;

There are two solutions:

  • Set class wrapped with synchronized of Collections tool class
  • Write replicated JUC solutions using CopyOnWriteArraySet
public class SetTest {
    public static void main(String[] args) {
        /**
         * 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * 2. Set<String> set = new CopyOnWriteArraySet<>();
         */
//        Set<String> set = new HashSet<>();
        Set<String> set = new CopyOnWriteArraySet<>();

        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

What is the bottom layer of HashSet?

The bottom layer of hashSet is a HashMap;

3) Map is not secure

//Is map used like this? No, I don't use this at work
//What is the default equivalent? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//Load factor, initialization capacity

The default load factor is 0.75 and the default initial capacity is 16

The same HashMap basic class also has concurrent modification exceptions!

public class MapTest {
    public static void main(String[] args) {
        //Is map used like this? No, I don't use this at work
        //What is the default equivalent? new HashMap<>(16,0.75);
        /**
         * Solution
         * 1. Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
         *  Map<String, String> map = new ConcurrentHashMap<>();
         */
        Map<String, String> map = new ConcurrentHashMap<>();
        //Load factor, initialization capacity
        for (int i = 1; i < 100; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

7. Callable

1. Can have return value;
2. Exceptions can be thrown;
3. Different methods, run()/call()

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 1; i < 10; i++) {
            MyThread1 myThread1 = new MyThread1();

            FutureTask<Integer> futureTask = new FutureTask<>(myThread1);
            // Put it into the Thread and the results will be cached
            new Thread(futureTask,String.valueOf(i)).start();
            // This get method may be blocked. If it is a time-consuming method in the call method, we will generally put this last or use asynchronous communication
            int a = futureTask.get();
            System.out.println("Return value:" + s);
        }

    }

}
class MyThread1 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}

8. Common auxiliary classes

1)CountDownLatch

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // The total is 6
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "==> Go Out");
                countDownLatch.countDown(); // Number of threads per thread - 1
            },String.valueOf(i)).start();
        }
        countDownLatch.await(); // Wait for the counter to zero and execute down
        System.out.println("close door");
    }
}

Main methods:

  • countDown minus one;
  • await waits for the counter to return to zero

await waits for the counter to return to zero, wakes up, and then continues to run down

2)CyclickBarrier

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // Main thread
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
            System.out.println("Summon the Dragon");
        });

        for (int i = 1; i <= 7; i++) {
            // Child thread
            int finalI = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "Collected the third" + finalI + "Dragon Ball");
                try {
                    cyclicBarrier.await(); // Add count wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3)Semaphore

public class SemaphoreDemo {
    public static void main(String[] args) {

        // Number of threads, parking space, current limiting
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i <= 6; i++) {
            new Thread(() -> {
                // acquire() gets
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "Grab a parking space");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "Leave the parking space");
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release(); // Release
                }
            }).start();
        }
    }
}

Principle:

semaphore.acquire() obtains the resource. If the resource has been used up, wait for the resource to be released before using it!

semaphore.release() releases the current semaphore by + 1, and then wakes up the waiting thread!

Function: mutually exclusive use of multiple shared resources! Concurrent flow restriction, control the maximum number of threads!

9. Read write lock

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        int num = 6;
        for (int i = 1; i <= num; i++) {
            int finalI = i;
            new Thread(() -> {

                myCache.write(String.valueOf(finalI), String.valueOf(finalI));

            },String.valueOf(i)).start();
        }

        for (int i = 1; i <= num; i++) {
            int finalI = i;
            new Thread(() -> {

                myCache.read(String.valueOf(finalI));

            },String.valueOf(i)).start();
        }
    }
}

/**
 *  Method is unlocked, resulting in queue jumping when writing
 */
class MyCache {
    private volatile Map<String, String> map = new HashMap<>();

    public void write(String key, String value) {
        System.out.println(Thread.currentThread().getName() + "The thread started writing");
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "Thread write ok");
    }

    public void read(String key) {
        System.out.println(Thread.currentThread().getName() + "The thread started reading");
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "Thread write read ok");
    }
}


2 The thread started writing
2 Thread write ok
3 The thread started writing
3 Thread write ok
1 The thread started writing    # A write from another thread was inserted, resulting in inconsistent data
4 The thread started writing
4 Thread write ok
1 Thread write ok
6 The thread started writing
6 Thread write ok
5 The thread started writing
5 Thread write ok
1 The thread started reading
1 Thread write read ok
2 The thread started reading
2 Thread write read ok
3 The thread started reading
3 Thread write read ok
4 The thread started reading
4 Thread write read ok
5 The thread started reading
6 The thread started reading
6 Thread write read ok
5 Thread write read ok

Process finished with exit code 0

Therefore, if we do not lock, multithreaded reading and writing will cause the problem of unreliable data.

We can also use synchronized heavy lock and light lock to ensure the reliability of data.

But this time we use a more fine-grained lock: ReadWriteLock read-write lock to ensure

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();
        int num = 6;
        for (int i = 1; i <= num; i++) {
            int finalI = i;
            new Thread(() -> {

                myCache.write(String.valueOf(finalI), String.valueOf(finalI));

            },String.valueOf(i)).start();
        }

        for (int i = 1; i <= num; i++) {
            int finalI = i;
            new Thread(() -> {

                myCache.read(String.valueOf(finalI));

            },String.valueOf(i)).start();
        }
    }

}
class MyCache2 {
    private volatile Map<String, String> map = new HashMap<>();
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void write(String key, String value) {
        lock.writeLock().lock(); // Write lock
        try {
            System.out.println(Thread.currentThread().getName() + "The thread started writing");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "Thread write ok");

        }finally {
            lock.writeLock().unlock(); // Release write lock
        }
    }

    public void read(String key) {
        lock.readLock().lock(); // Read lock
        try {
            System.out.println(Thread.currentThread().getName() + "The thread started reading");
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "Thread write read ok");
        }finally {
            lock.readLock().unlock(); // Release read lock
        }
    }
}

1 The thread started writing
1 Thread write ok
6 The thread started writing
6 Thread write ok
3 The thread started writing
3 Thread write ok
2 The thread started writing
2 Thread write ok
5 The thread started writing
5 Thread write ok
4 The thread started writing
4 Thread write ok
    
1 The thread started reading
5 The thread started reading
2 The thread started reading
1 Thread write read ok
3 The thread started reading
2 Thread write read ok
6 The thread started reading
6 Thread write read ok
5 Thread write read ok
4 The thread started reading
4 Thread write read ok
3 Thread write read ok

Process finished with exit code 0

10. Blocking queue

1)BlockQueue

Is a subclass of Collection

When do we use blocking queues

Multithreaded concurrent processing, thread pool

BlockingQueue has four sets of APIs

/**
     * Throw exception
     */
    public static void test1(){
        //The size of the queue that needs to be initialized
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        //Throw exception: java.lang.IllegalStateException: Queue full
//        System.out.println(blockingQueue.add("d"));
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //If you remove one more
        //This will also cause java.util.NoSuchElementException to throw an exception
        System.out.println(blockingQueue.remove());
    }
=======================================================================================
/**
     * No exception is thrown and there is a return value
     */
    public static void test2(){
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //Adding an element that cannot be added, using offer will only return false and will not throw an exception
        System.out.println(blockingQueue.offer("d"));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        //If there are no pop-up elements, it will only return null and will not throw an exception
        System.out.println(blockingQueue.poll());
    }
=======================================================================================
/**
     * Waiting has been blocked
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        //Always blocked and will not return
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");

        //If the queue is full, another element will be entered. In this case, the program will wait until when the queue has a position to enter again, and the program will not stop
//        blockingQueue.put("d");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //If we do it again, this situation will also wait, and the program will keep running and blocking
        System.out.println(blockingQueue.take());
    }
=======================================================================================
/**
     * Wait timeout blocking
     *  In this case, it will also wait for the queue to have a location or a product, but the timeout will end
     */
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        System.out.println("Start waiting");
        blockingQueue.offer("d",2, TimeUnit.SECONDS);  //The timeout is 2s. If it exceeds 2s, the wait will end
        System.out.println("End waiting");
        System.out.println("===========Value==================");
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println("Start waiting");
        blockingQueue.poll(2,TimeUnit.SECONDS); //We won't wait for more than two seconds
        System.out.println("End waiting");
    }


2) Synchronous queue

If the synchronization queue has no capacity, it can also be regarded as a queue with capacity of 1;

When you enter an element, you must wait for it to be taken out before you can put another element in it;

put method and take method;

Unlike other blockingqueues, Synchronized does not store elements;

If you put an element, you must take it out first, otherwise you can't put in the value!

And the take of SynchronousQueue uses lock lock to ensure thread safety.

public class SynchronousQueue {
    public static void main(String[] args) {
        BlockingQueue<String> synchronousQueue = new java.util.concurrent.SynchronousQueue<>();
        // Add element to net queue
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "put 01");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "put 02");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "put 03");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        // Extract element
        new Thread(()-> {
            try {
                System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
                System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
                System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
Thread-1take1
Thread-0put 02
Thread-1take2
Thread-0put 03
Thread-1take3

Process finished with exit code 0

11. Thread pool (important)

Thread pool: three methods, seven parameters and four rejection strategies

The operation of the program, essence: occupy the resources of the system! We need to optimize the use of resources = = > pooling technology

Thread pool, JDBC connection pool, memory pool, object pool, etc....

The creation and destruction of resources are very resource consuming

Pooling Technology: prepare some resources in advance. If someone wants to use them, come to me and return them to me after use, so as to improve efficiency.

1) Benefits of thread pool:

1. Reduce resource consumption;

2. Improve response speed;

3. Convenient management;

Thread reuse, maximum concurrent number can be controlled and threads can be managed;

2) Thread pool: three methods

  • ExecutorService threadPool = Executors.newSingleThreadExecutor();// Single thread
  • ExecutorService threadPool2 = Executors.newFixedThreadPool(5); // Create a fixed thread pool size
  • ExecutorService threadPool3 = Executors.newCachedThreadPool(); // Scalable
//Three methods of tool Executors;
public class Demo01 {
    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newSingleThreadExecutor();//Single thread
        ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //Create a fixed thread pool size
        ExecutorService threadPool3 = Executors.newCachedThreadPool(); //Scalable

        //When the thread pool runs out, you must close the thread pool
        try {

            for (int i = 1; i <=100 ; i++) {
                //Creating threads from a thread pool
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+ " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

3) Seven parameters

Source code analysis of newSingleThreadExecutor()

 /**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }


Source code analysis of newFixedThreadPool()

 /**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

Source code analysis of newCachedThreadPool()

 /**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

You can see that the bottom layer of the three methods is new ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,  //Core thread pool size
                          int maximumPoolSize, //Maximum thread pool size
                          long keepAliveTime,  //If no one calls it, it will be released
                          TimeUnit unit, //Timeout unit
                          BlockingQueue<Runnable> workQueue, //Blocking queue
                          ThreadFactory threadFactory, //Thread factories generally do not need to move when creating threads
                          RejectedExecutionHandler handler //Reject policy
                         ) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

Alibaba's Java operation manual clearly states that for integer.max_ The initial value of value is large, so generally, we use the underlying ThreadPoolExecutor to create a thread pool.

Simulate the above banking business
The core thread size is set to 2: it is the window that works all the time
The maximum thread is set to 5: it is the working window with the most banks
keepAliveTime is set to 1 hour: if there is no business for 1 hour, close the window
Waiting area: new LinkedBlockingQueue(3), assuming that there are at most 3 people in the waiting area
Thread factory: just use the default, executors. Defaultthreafaactory()
Rejection policy: you can find four rejection policies. Use the default AbortPolicy() / / the bank is full, but if someone else comes in, don't deal with this person and throw an exception

public class PollDemo {
    public static void main(String[] args) {
        // Gets the number of cores of the cpu
        int max = Runtime.getRuntime().availableProcessors();
        ExecutorService service =new ThreadPoolExecutor(
                2,
                max,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            for (int i = 1; i <= 10; i++) {
                service.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            service.shutdown();
        }
    }
}

4) Reject policy

  1. new ThreadPoolExecutor.AbortPolicy() / /: the rejection policy is: if the bank is full and someone else comes in, don't handle this person's and throw an exception

If the maximum load is exceeded, an exception will be thrown: queue capacity size + maxPoolSize

  1. new ThreadPoolExecutor.CallerRunsPolicy() / / the rejection policy is: where to go, where to go. The main thread handles it

  2. new ThreadPoolExecutor.DiscardPolicy(): / / the rejection policy is: if the queue is full, throw the exception, and no exception will be thrown.

  3. New ThreadPoolExecutor. Discardolddestpolicy() / / the rejection policy is: if the queue is full, try to compete with the earliest process without throwing exceptions

5) How to set the size of the thread pool

1. CPU intensive: the number of cores of a computer can be selected according to the number of cores; Select the size of the maximunPoolSize

// Gets the number of cores of the cpu
        int max = Runtime.getRuntime().availableProcessors();
        ExecutorService service =new ThreadPoolExecutor(
                2,
                max,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

2. I/O intensive:

There are 15 large tasks in the program, which io takes up a lot of resources; I/O intensive is to judge the number of I/O consuming threads in our program, which is about twice to twice the maximum number of I/O.

12. Four functional interfaces

Programmers in the new era: lambda expression, chain programming, functional interface, Stream flow computing

Functional interface: an interface with only one method

Simplify the programming model and apply it in a large number at the bottom of the new version of the framework

Foreach (functional interface of consumer type)

1) Function function interface

public class FunctionDemo {
    public static void main(String[] args) {
        Function<String, String> function = (str) -> {return str;};
        System.out.println(function.apply("aaaaaaaaaa"));
    }
}

2) Predicate type interface

public class PredicateDemo {
    public static void main(String[] args) {
        Predicate<String> predicate = (str) -> {return str.isEmpty();};
        // false
        System.out.println(predicate.test("aaa"));
        // true
        System.out.println(predicate.test(""));
    }
}

3) Supplier supply interface

/**
 * Supply type interface, return only, no input
 */
public class Demo4 {
    public static void main(String[] args) {
        Supplier<String> supplier = ()->{return "1024";};
        System.out.println(supplier.get());
    }
}


4) Consumer interface

/**
 * Consumer interface has no return value! Only input!
 */
public class Demo3 {
    public static void main(String[] args) {
        Consumer<String> consumer = (str)->{
            System.out.println(str);
        };
        consumer.accept("abc");
    }
}


13. Stream flow calculation

What, Stream streaming

Big data: storage + computing

The essence of collection and Mysql is to calculate and store things

All calculations should be left to the flow

/**
 * Description: 
 * Title Requirements: implemented in one line of code
 * 1. Id Must be even
 * 2.Age must be greater than 23
 * 3. Change user name to uppercase
 * 4. User names in reverse order
 * 5. Only one user can be output
 *
 * @author jiaoqianjin
 * Date: 2020/8/12 14:55
 **/

public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(1, "a", 23);
        User u2 = new User(2, "b", 23);
        User u3 = new User(3, "c", 23);
        User u4 = new User(6, "d", 24);
        User u5 = new User(4, "e", 25);

        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        // lambda, chain programming, functional interface, flow computing
        list.stream()
                .filter(user -> {return user.getId()%2 == 0;})
                .filter(user -> {return user.getAge() > 23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((user1, user2) -> {return user2.compareTo(user1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

14. ForkJoin (branch merge)

ForkJoin executes tasks in parallel in JDK1.7! Improve efficiency ~. In large amounts of data, the speed will be faster!

In big data: MapReduce core idea - > split big tasks into small tasks!

1) ForkJoin features: work theft!

The implementation principle is: double ended queue! You can get the task from above and below for execution!

2) How do I use ForkJoin?

  • 1. Execute through ForkJoinPool

  • 2. Compute task execute (forkjointask <? > task)

  • 3. The calculation class should inherit ForkJoinTask;

Calculation class of ForkJoin

public class ForkJoinDemo extends RecursiveTask<Long> {
    private long star;
    private long end;
    /** critical value */
    private long temp = 1000000L;

    public ForkJoinDemo(long star, long end) {
        this.star = star;
        this.end = end;
    }

    /**
     * computing method
     * @return
     */
    @Override
    protected Long compute() {
        if ((end - star) < temp) {
            Long sum = 0L;
            for (Long i = star; i < end; i++) {
                sum += i;
            }
            return sum;
        }else {
            // Divide and conquer calculation using ForkJoin
            //1. Calculate the average value
            long middle = (star + end) / 2;
            ForkJoinDemo forkJoinDemo1 = new ForkJoinDemo(star, middle);
            // Split the task and push the thread into the thread queue
            forkJoinDemo1.fork();
            ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo(middle+1,end);
            forkJoinDemo2.fork();

            long taskSum = forkJoinDemo1.join() + forkJoinDemo2.join();
            return taskSum;
        }
    }
}

Test class


public class ForkJoinTest {
    private static final long SUM = 20_0000_0000;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    /**
     * Use common methods
     */
    public static void test1() {
        long star = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 1; i < SUM ; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println(sum);
        System.out.println("Time:" + (end - star));
        System.out.println("----------------------");
    }
    /**
     * Using the ForkJoin method
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long star = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, SUM);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long along = submit.get();

        System.out.println(along);
        long end = System.currentTimeMillis();
        System.out.println("Time:" + (end - star));
        System.out.println("-----------");
    }
    /**
     * Using Stream flow calculation
     */
    public static void test3() {
        long star = System.currentTimeMillis();

        long sum = LongStream.range(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);
        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println("Time:" + (end - star));
        System.out.println("-----------");
    }
}

. parallel().reduce(0, Long::sum) uses a parallel stream to calculate the whole calculation to improve efficiency.

15. Asynchronous callback

The original intention of Future design: modeling the result of an event in the Future!

In fact, the front end -- > sends ajax asynchronous requests to the back end

But we usually use completable future

(1) runAsync asynchronous callback with no return value

public static void main(String[] args) throws ExecutionException, InterruptedException 
{
        // Initiate a request

        System.out.println(System.currentTimeMillis());
        System.out.println("---------------------");
        CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
            //Initiate an asynchronous task
            try { 
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+".....");
        });
        System.out.println(System.currentTimeMillis());
        System.out.println("------------------------------");
        //Output execution results
        System.out.println(future.get());  //Get execution results
 }

(2) Asynchronous callback supplyAsync with return value

//Asynchronous callback with return value
CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{
    System.out.println(Thread.currentThread().getName());
    try {
        TimeUnit.SECONDS.sleep(2);
        int i=1/0;
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
    //success callback
    System.out.println("t=>" + t); //Normal return result
    System.out.println("u=>" + u); //Error message for throwing exception
}).exceptionally((e) -> {
    //error callback
    System.out.println(e.getMessage());
    return 404;
}).get());

whenComplete: there are two parameters, one is t and the other is u

T: Is the result of the normal return of the representative;

U: Is the error message that represents the exception thrown;

If an exception occurs, get can get the value returned by exceptionally;

16. JMM

1) Understanding of Volatile

Volatile is a lightweight synchronization mechanism provided by Java virtual machine

1. Ensure visibility
2. Atomicity is not guaranteed
3. Prohibit instruction rearrangement

How to achieve visibility

The shared variable modified by volatile variable returns one more line of assembly when writing:

0x01a3de1d:movb $0×0,0×1104800(%esi);0x01a3de24**:lock** addl $0×0,(%esp);

Instructions prefixed with Lock cause two things in multi-core processors.

1) Writes the data of the current processor cache line back to the system memory.

2) This write back operation will invalidate the data of the memory address cached in other CPUs.

Multiprocessor bus sniffing:

In order to improve the processing speed, the processor does not directly communicate with the memory, but reads the data in the system memory to the internal cache before operation, but the operation does not know when it will be written to the memory. If you write a variable declared volatile, the JVM will send a lock prefix instruction to the processor to write the data of the cache line where the variable is located back to the system memory. However, in multiprocessors, in order to ensure that the cache of each processor is consistent, the cache consistency protocol will be implemented. Each processor checks whether its cache value is expired by sniffing the data transmitted on the bus. If the processor finds that the memory address corresponding to its cache line is modified, it will set the cache line of the current processor to an invalid state, When the processor modifies this data, it will read the database from the system memory to the processor cache again.

2) What is JMM?

JMM: JAVA Memory Model, which does not exist, is a concept and an agreement!

Some synchronization conventions of JMM:

1. Before the thread is unlocked, the shared variable must be immediately flushed back to main memory;

2. Before locking, the thread must read the latest value in the main memory into the working memory;

3. Locking and unlocking are the same lock;

Threads are divided into working memory and main memory

8 operations:

  • Read: acts on the main memory variable, which transfers the value of a variable from the main memory to the working memory of the thread for subsequent load action;
  • load: the variable that acts on the working memory. It puts the read operation from the main memory into the working memory;
  • Use: it acts on the variables in the working memory. It transfers the variables in the working memory to the execution engine. Whenever the virtual machine encounters a value that needs to be used, it will use this instruction;
  • assign: a variable that acts on the working memory. It puts a value received from the execution engine into the variable copy of the working memory;
  • store: a variable that acts on the main memory. It transfers the value of a variable from the working memory to the main memory for subsequent write;
  • write: a variable that acts on the main memory. It puts the value of the variable obtained from the working memory by the store operation into the variable of the main memory;
  • lock: a variable that acts on the main memory and identifies a variable as a thread exclusive state;
  • unlock: a variable that acts on the main memory. It releases a locked variable, and the released variable can be locked by other threads;

It is possible that thread B modifies the value and flushes it back to main memory, but thread A does not get the latest value

JMM provides corresponding regulations for these 8 operations:

  • One of read and load, store and write operations is not allowed to appear alone. That is, read must be loaded and store must be written
  • The thread is not allowed to discard its latest assign operation, that is, after the data of the work variable has changed, it must inform the main memory
  • A thread is not allowed to synchronize data without assign from working memory back to main memory
  • A new variable must be born in main memory. Working memory is not allowed to directly use an uninitialized variable. This means that the variables must be assign ed and load ed before the use and store operations
  • Only one thread can lock a variable at a time. After multiple locks, you must perform the same number of unlocks to unlock
  • If you lock a variable, the value of this variable in all working memory will be cleared. Before the execution engine uses this variable, you must re load or assign to initialize the value of the variable
  • If a variable is not locked, it cannot be unlocked. You cannot unlock a variable that is locked by another thread
  • Before unlock ing a variable, you must synchronize the variable back to main memory

17. volatile

1) Ensure visibility

public class JMMDemo {
    // Without volatile, the program will loop!
    // Adding volatile ensures visibility
   // private volatile static int num = 0;
    private  static int num = 0;

    public static void main(String[] args) { // main

        new Thread(()->{ // Thread 1 is not aware of changes in main memory
            while (num==0){

            }
        }).start();

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

        num = 1;
        System.out.println(num);

    }
}


But if you add a statement to print i in the while loop

while(num==0)
 {
  System.out.println(num);
 }

The program stopped after a while

This is because the child thread forces the value of i to be read once

When printing and outputting statements in the loop body, the println method has synchronized synchronization code blocks, and synchronized can also ensure the visibility of variable modification. After the operation of variable num, the value of num will be immediately brushed back to main memory

Adding an output statement to the sub thread will cause the loop in the run method to judge every output. When the memory data is modified and judged after the cache consistency protocol takes effect, the loop will exit

2) Atomicity is not guaranteed

Atomicity: indivisible
Thread A cannot be disturbed when executing tasks. It either succeeds or fails at the same time

public class Vdemo02 {
    private static int num=0;
    public static void add()
    {
        num++;
    }
    public static void main(String[] args) {
        for(int i=0;i<20;i++)
        {
            new Thread(()->{
                for(int j=0;j<1000;j++)
                {
                    add();
                }
            }).start();
        }

        while(Thread.activeCount()>2)//Because there are always two threads in the program, the main thread and the gc thread
        {
            Thread.yield();
            //Thread.yield() is executed in the main thread, which means that there is still a way out. If other threads other than GC and main are running, the main thread will give up the cpu and not execute down. Then it will give up and re compete for the execution right of the cpu. It is possible that the main grabs it, but this is a loop. The grabbing continues to give up until there are only two threads
        }
        //Theoretically, it should be 20000
        System.out.println(Thread.currentThread().getName()+" "+num);//Definitely not directly
    }
}

resolvent
We can add synchronized

 public synchronized static void add()
    {
        num++;
    }

If you just add volatile to the variable

  private volatile static int num=0;

Note that + + is not an atomic operation
How to ensure atomicity without lock and synchronized?

Let's take a look at how the underlying jvm operates for + +

Method 1:

Method 2:
Select the class file in the out directory, and then select show byteCode. Without this option, you need to install the plug-in

Method 3:

public static void add();
    Code:
       0: getstatic     #2                  // Field num:I
       3: iconst_1
       4: iadd
       5: putstatic     #2                  // Field num:I
       8: return

Let's analyze the bytecode operation of + +

Step 1: get the value of num
Step 2: add 1 to the value of num
Step 3: write back num

In the process of multi-threaded operation, the value after adding 1 may be read by another thread to add 1 before it can be updated. In other words, some threads use the previous value, so the overall sum will be less than 20000

The reason why it is less than the theoretical value is that when the thread writes back the data to the main memory, it overwrites the results that have been executed by other faster threads

If num is 0, thread 1 may add 1, and thread 2 will get it before writing it back. At this time, Num is still 0, so both threads operate num, but the value of num is 1, which is equivalent to adding it only once

For threads, it is only guaranteed to access the current value from the master before each addition
The number of additions is fixed. Some threads take the old value, and the result must be small

Back to the question just now, how to ensure atomicity without lock and synchronized?

Using atomic classes to solve atomic problems

 private  static AtomicInteger num=new AtomicInteger();
    public  static void add()
    {
        num.getAndIncrement(); //cas
        //num=num+1;
    }

Because when the atomic class is initialized at the bottom, it is assigned to a volatile attribute
The bottom layers of atomic classes are directly linked to the operating system and modify values in memory. Unsafe class is a very special existence

 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

3) Prohibit instruction rearrangement

What is instruction rearrangement?

The computer does not execute the programs we write as we write

Source code – > compiler optimization rearrangement – > instruction parallelism may also rearrange – > memory system may also rearrange – > execution

When the processor rearranges instructions, it will consider the dependence between data!

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4

//The expected execution order is 1_ 2_ 3_ 4 the possible execution sequence will become 2134 1324
//Could it be 4123? impossible

Possible impact result: premise: the four values of a, B, x, y are all 0 by default

volatile avoids instruction rearrangement:

volatile will add a memory barrier, which can ensure the order of instructions in this barrier.

Memory barrier: CPU instruction. effect:

1. Ensure the execution sequence of specific operations;

2. Memory visibility of some variables can be guaranteed (with these features, the visibility of volatile implementation can be guaranteed)

After volatile annotation, the compiler generated code will not be optimized

Add SS barrier before writing, SL barrier after writing, LL barrier before reading and LS barrier after reading

4) Summary

  • volatile ensures visibility;
  • Atomicity cannot be guaranteed
  • Due to the memory barrier, instruction rearrangement can be avoided

Interviewer: do you know where to use this memory barrier most? Singleton mode

18. Play single instance mode

Hungry, DCL lazy

When it comes to volatile's prevention of instruction rearrangement, where volatile's memory barrier is most used is the singleton mode.

1) Hungry Han style

Hungry man problem: it may waste memory
As soon as the hungry man type comes up, it will load all things into memory, and the object already exists. If the object is not used, it may waste memory

The main features are:

Constructors are private to avoid creating objects outside
Create objects in advance
Provide a public interface to return objects created in advance within the class
Static variables are instantiated with the loading of the class, which has nothing to do with whether to call static methods
The hungry type will be initialized when loading, and the lazy type will be initialized only when obtaining a singleton
When a class is loaded, member variables are initialized and local variables are not

/**
 * Hungry Han style single case
 */
public class Hungry {

    /**
     * It may waste space
     */
    private byte[] data1=new byte[1024*1024];
    private byte[] data2=new byte[1024*1024];
    private byte[] data3=new byte[1024*1024];
    private byte[] data4=new byte[1024*1024];



    private Hungry(){

    }
    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }

}

2) DCL lazy

In view of the memory waste of hungry singleton, a lazy singleton is proposed, which creates objects when it needs to be used

public class LazyMan {
    private LazyMan(){

    }
    private static LazyMan lazyman;//No object has been created, it's just a declaration, no new

    public static LazyMan getInstance()
    {
      if(lazyman==null)
       {
         lazyman=new LazyMan();//If the object is empty, instantiate the object
       }
      return lazyman;
    }
}

In the case of multiple threads, the lazy singleton may have a security problem, that is, thread 1 enters the if judgment and starts to construct objects

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }
    private static LazyMan lazyman;//The object has not been created yet, it is just declared, and there is no new

    public static LazyMan getInstance()
    {
      if(lazyman==null)
       {
         lazyman=new LazyMan();//If the object is empty, instantiate the object
       }
      return lazyman;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();

        }
    }
}


You can see that three threads call the constructor, which means that there are three Lazyman objects in the program, which is not a singleton, so it is not safe

Double lock mechanism

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }
    private static LazyMan lazyman;//The object has not been created yet, it is just declared, and there is no new

    public static LazyMan getInstance()
    {
        //Double detection lock mode DCL
        if(lazyman==null)
        {
            //If it is empty, lock the current LazyMan object with the previous lock
            synchronized (LazyMan.class){//Static methods are class locks
              //If synchronized is written directly on the method, all threads will rob the lock, which is inefficient. This will rob the lock only when it is empty
                if(lazyman==null)//Judge again in the lock
                {
                    lazyman=new LazyMan();//If the object is empty, instantiate the object
                }
            }
        }

      return lazyman;
    }

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

        }
    }
}



You can see that only one object is created

But there may still be problems

In extreme cases, the process of creating objects will certainly have problems, because it is not an atomic operation and will experience
1 allocate memory space,
2 execute construction method (initialization object)
3 point the object to the allocated space

However, instruction rearrangement may occur, which may be executed in the order of 132, that is, allocate memory space first, then occupy memory space with empty objects, and then execute the construction method

As shown in the following figure, it is likely that A has executed 13 but not 2, but now the lazyman is not null. If A B thread comes in and the outer layer is judged not to be empty, then the B thread will directly return to the lazyman, but the lazyman has not actually completed the construction, so it is unsafe (new just adds the application, but the heap has not been created, so there will be A problem with return)

Therefore, the volatile modification should be used to prevent instruction rearrangement (prevent the second thread from executing first and returning a reference that has not been initialized)
So here is the synchronous code block, which ensures the atomicity of the operation, and volatile prohibits instruction rearrangement
The principle of instruction rearrangement is to improve the efficiency of CPU multi-stage pipeline, but it is not arbitrary rearrangement of instructions. The processor must be able to correctly deal with instruction dependencies to ensure that the program obtains correct execution results.

Summary: synchronized ensures that the if judgment and the new object can succeed or fail at the same time, but the new object is not an atomic operation. After 13, the second thread considers that the new object has succeeded, and the top if judgment is not null

3) Static inner class

Write a static class in a class
First, as long as the singleton is private, the constructor must be private first
Static inner classes are not loaded when external classes are loaded
Thread safe and lazy loading
However, static inner class singletons are also unsafe because reflection can destroy singletons

//Static inner class
public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.holder;
    }
    public static class InnerClass{
        private static final Holder holder = new Holder();
    }
}

test

public class Holder {
    private Holder() {
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    public static Holder getInstance() {
        return InnerClass.HOLDER;
    }

    public static class InnerClass {
        private static final Holder HOLDER = new Holder();
    }

    public static void main(String[] args) {
       for(int i=0;i<10;i++)
       {
           new Thread(()->{
               Holder.getInstance();
           }).start();
       }
    }
}

You can see that there is only one instance in memory, that is, only one thread enters the constructor, because the static class is loaded only once

However, as long as there is reflection, any private is a paper tiger. Let's take the single example of DCL as an example to try reflection

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//             LazyMan.getInstance();
//              //  System.out.println(LazyMan.getInstance());
//            }).start();
    //    }
        LazyMan instance = LazyMan.getInstance();
        //Get null parameter constructor
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        //Ignore private constructor
        declaredConstructor.setAccessible(true);
        //Create objects by reflection
        LazyMan lazyMan = declaredConstructor.newInstance();

        //Test whether the two objects are the same
        System.out.println( instance );
        System.out.println(lazyMan);
    }

Can be cracked:

private LazyMan(){
                synchronized (LazyMan.class){

                    if(lazyman!=null)
                    {
                        throw new RuntimeException("Do not attempt to destroy a singleton by reflection");
                    }
                    System.out.println(Thread.currentThread().getName()+"ok");

        }

Look at the complete code
It is equivalent to adding a double detection in the constructor on the basis of DCL

public class LazyMan {

    private LazyMan() {
        synchronized (LazyMan.class) {

            if (lazyman != null) {
                throw new RuntimeException("Do not attempt to destroy a singleton by reflection");
            }
            System.out.println(Thread.currentThread().getName() + "ok");
        }
        //
    }

    private static volatile LazyMan lazyman;//The object has not been created yet, it is just declared, and there is no new

    public static LazyMan getInstance() {
        //Double detection lock mode DCL
        if (lazyman == null) {
            //If it is empty, lock the current LazyMan object with the previous lock
            synchronized (LazyMan.class) {//Static methods are class locks
                //If synchronized is written directly on the method, all threads will rob the lock, which is inefficient. This will rob the lock only when it is empty
                if (lazyman == null)//Judge again in the lock
                {
                    lazyman = new LazyMan();//If the object is empty, instantiate the object
                }
            }
        }

        return lazyman;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//             LazyMan.getInstance();
//              //  System.out.println(LazyMan.getInstance());
//            }).start();
        //    }
        LazyMan instance = LazyMan.getInstance();
        //Get null parameter constructor
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        //Ignore private constructor
        declaredConstructor.setAccessible(true);
        //Create objects by reflection
        LazyMan lazyMan = declaredConstructor.newInstance();

        //Test whether the two objects are the same
        System.out.println(instance);
        System.out.println(lazyMan);
    }
}

Now we don't need getInstance() to get the object, but create two objects directly through reflection

It can be found that the singleton is broken again, because what is judged in the constructor is

 if (lazyman == null)//Judge again in the lock
                {
                }

However, note that the object we use to reflect new must be different from the lazyman object in the class. The lazyman in the class is always empty without calling getInstance(), so the singleton is destroyed again

Solution, use a flag bit

private static boolean flag=false;
    private LazyMan() {
        synchronized (LazyMan.class) {

            if( flag==false)
            {
                flag=true;
            }
            else
            {
                throw new RuntimeException("Do not attempt to destroy a singleton by reflection");
            }
        }
    }

Let's continue to destroy the singleton. We destroy the flag field

  Field  flag=LazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        //Ignore private constructor
        declaredConstructor.setAccessible(true);
        //Create objects by reflection
        LazyMan lazyMan = declaredConstructor.newInstance();
        flag.set(lazyMan,false);//Change the flag of the first object to false again
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        //Test whether the two objects are the same
       // System.out.println(instance);
        System.out.println(lazyMan);
        System.out.println(lazyMan2);

It can be found that the single case was destroyed again....

public class LazyMan {
private static boolean flag=false;
    private LazyMan() {
        synchronized (LazyMan.class) {

            if( flag==false)
            {
                flag=true;
            }
            else
            {
                throw new RuntimeException("Do not attempt to destroy a singleton by reflection");
            }
        }
    }

    private static volatile LazyMan lazyman;//The object has not been created yet, it is just declared, and there is no new

    public static LazyMan getInstance() {
        //Double detection lock mode DCL
        if (lazyman == null) {
            //If it is empty, lock the current LazyMan object with the previous lock
            synchronized (LazyMan.class) {//Static methods are class locks
                //If synchronized is written directly on the method, all threads will rob the lock, which is inefficient. This will rob the lock only when it is empty
                if (lazyman == null)//Judge again in the lock
                {
                    lazyman = new LazyMan();//If the object is empty, instantiate the object
                }
            }
        }
        return lazyman;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//             LazyMan.getInstance();
//              //  System.out.println(LazyMan.getInstance());
//            }).start();
        //    }
        // LazyMan instance = LazyMan.getInstance();
//Get null parameter constructor

        Field  flag=LazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        //Ignore private constructor
        declaredConstructor.setAccessible(true);
        //Create objects by reflection
        LazyMan lazyMan = declaredConstructor.newInstance();
        flag.set(lazyMan,false);//Change the flag of the first object to false again
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        //Test whether the two objects are the same
       // System.out.println(instance);
        System.out.println(lazyMan);
        System.out.println(lazyMan2);
    }
}



How to solve it? Let's click in the reflected newInstance() to see

We can see that if the class is an enumeration type, it will tell you that you can't use reflection to destroy enumeration. Enumeration began to appear in jdk 1.5 with its own singleton mode

4) Enumeration

Enumeration itself is also a class

public enum EnumSingle {
    INSTANCE;
   public static EnumSingle getInstance()
   {
       return INSTANCE;
   }
}
 class Test{
     public static void main(String[] args) {
         EnumSingle  instance1=EnumSingle.INSTANCE;
         EnumSingle  instance2=EnumSingle.INSTANCE;
         EnumSingle  instance3=EnumSingle.getInstance();
         System.out.println( instance1);
         System.out.println( instance2);
         System.out.println( instance3);
     }
 }

Let's try the reflection violation enumeration singleton

   

public enum EnumSingle {
    INSTANCE;
   public static EnumSingle getInstance()
   {
       return INSTANCE;
   }
 }
 class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
		EnumSingle  instance1=EnumSingle.INSTANCE;
//         EnumSingle  instance2=EnumSingle.INSTANCE;
//         EnumSingle  instance3=EnumSingle.getInstance();

         Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true);//First, break the private permission of the constructor so that the reflection can access and create objects
         EnumSingle instance2= declaredConstructor.newInstance();
         System.out.println( instance1);
         System.out.println( instance2);
     }
 }

The following error message is that the enumeration class has no null parameter constructor
idea lied to us

 Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);

The normal destruction singleton should report an error, and the reflection destruction enumeration cannot be used

Through decompilation, we can see that the enumeration itself is also a class, which inherits an enumeration class
However, the constructor is still empty, which means that we have been cheated

Now let's decompile with jad.exe

Let's take a look at the class bytecode generated java file

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

package juc.single;


public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(juc/single/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
        super(s, i);
    }

    public static EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}


You can see that it is not a parameterless constructor, but a parameterless constructor, with a String and an Int

Now let's modify the reflection code

 Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);

We get the result we want. Throw a singleton exception where reflection cannot destroy the enumeration

public enum EnumSingle {
    INSTANCE;
   public static EnumSingle getInstance()
   {
       return INSTANCE;
   }
}
 class Test{
     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
         EnumSingle  instance1=EnumSingle.INSTANCE;
//         EnumSingle  instance2=EnumSingle.INSTANCE;
//         EnumSingle  instance3=EnumSingle.getInstance();

        // Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
         Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
         declaredConstructor.setAccessible(true);//First, break the private permission of the constructor so that the reflection can access and create objects
         EnumSingle instance2= declaredConstructor.newInstance();
         System.out.println( instance1);
         System.out.println( instance2);
     }
 }

19. In depth understanding of CAS

1) What is CAS?

Big factories must deeply study the bottom!!!! Cultivate internal skills! Operating system, computer network principle, composition principle, data structure

public class casDemo {
    //CAS: compareandset compare and exchange
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        //boolean compareAndSet(int expect, int update)
        //Expected value, updated value
        //If the actual value is the same as my expectation, update it
        //If the actual value is different from my expectation, it will not be updated
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //Because the expected value is 2020, but the actual value becomes 2021, the modification will fail
        //CAS is the concurrency primitive of CPU
        atomicInteger.getAndIncrement(); //++Operation
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

Java cannot operate memory, but C + + can operate memory. Java can call C + + through native methods to operate memory

Unsafe class

Summary: CAS: compare the value in the current working memory with the value in the main memory. If the value is expected, the operation will be executed. If not, the loop will continue

Advantage: you don't need to switch thread state, because switching thread state consumes a lot of performance
Disadvantages:
1: Because the bottom layer is spin lock, the loop will waste time
2: Because it is the underlying cpu operation, it can only ensure the atomicity of one shared variable at a time
3: ABA problem

20. Atomic citation to solve ABA problem

CAS: ABA problem? (civet cat for Prince)

Thread 1: the expected value is 1 and becomes 2;

Thread 2: two operations:

  • 1. The expected value is 1 and becomes 3
  • 2. The expectation is 3 and becomes 1

Therefore, for thread 1, the value of A is still 1, so there is A problem and deceived thread 1;

public class casDemo {
    //CAS: compareandset compare and exchange
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //boolean compareAndSet(int expect, int update)
        //Expected value, updated value
        //If the actual value is the same as my expectation, update it
        //If the actual value is different from my expectation, it will not be updated
        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());

        //Because the expected value is 2020, but the actual value becomes 2021, the modification will fail
        //CAS is the concurrency primitive of CPU
//        atomicInteger.getAndIncrement(); //++ operation
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

public class CASDemo {
    /**AtomicStampedReference Note that if the generic is a wrapper class, pay attention to the reference of the object
     * In normal business operations, objects are compared one by one
     */
    static AtomicStampedReference<Integer> atomicStampedReference = new
            AtomicStampedReference<>(1, 1);

    // CAS compareAndSet: compare and exchange!
    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // Get version number
            System.out.println("a1=>" + stamp);
            
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // During modification, the version number is updated by + 1
            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
            
            System.out.println("a2=>" + atomicStampedReference.getStamp());
            // Change the value back and update the version number + 1
            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1));
            System.out.println("a3=>" + atomicStampedReference.getStamp());
        }, "a").start();
        
        // The principle of optimistic lock is the same!
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // Get version number
            System.out.println("b1=>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 3,
                    stamp, stamp + 1));
            System.out.println("b2=>" + atomicStampedReference.getStamp());
        }, "b").start();
    }
}


21. Understanding of various locks

1) Fair lock

  1. Fair lock: very fair. You can't jump the queue. You must come first
/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

  1. Unfair lock: very unfair. Queue jumping is allowed and the order can be changed. synchronized and lock are both non fair locks by default
/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2) Reentrant lock

All locks are reentrant locks, which are called recursive locks in some places

When you enter your house and get the lock of the front door, you automatically get the lock of the small door inside

  1. Synchronized lock
public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"=> sms");
        call();//There is also a lock here
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}

  1. Lock lock
//lock
public class Demo02 {

    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}
class Phone2{

    Lock lock=new ReentrantLock();

    public void sms(){
        lock.lock(); //Details: This is two locks and two keys
        //The lock lock must be paired, otherwise it will deadlock in it
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//There is also a lock here
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=> call");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}

  • lock locks must be paired, which means that the number of locks and unlock s must be the same;
  • The lock added outside can also be unlocked inside; The lock added inside can also be unlocked outside;

3) Spin lock

  1. spinlock
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

  1. Self designed spin lock
public class SpinlockDemo {

    // default
    // int 0
    //thread null
    AtomicReference<Thread> atomicReference=new AtomicReference<>();

    //Lock
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.currentThread().getName()+"===> mylock");

        //Spin lock
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName()+" ==> In spin~");
        }
    }


    //Unlock
    public void myUnlock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.currentThread().getName()+"===> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }

}

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
        //ReentrantLock reentrantLock = new ReentrantLock();
        //reentrantLock.lock();
        //reentrantLock.unlock();


        //Spin lock using CAS
        SpinlockDemo spinlockDemo=new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myUnlock();
            }
        },"t1").start();

        TimeUnit.SECONDS.sleep(1);


        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myUnlock();
            }
        },"t2").start();
    }
}

The t2 process must wait for the t1 process to Unlock before it can Unlock. Before that, wait

4) Deadlock

public class DeadLock {
    public static void main(String[] args) {
        String lockA= "lockA";
        String lockB= "lockB";

        new Thread(new MyThread(lockA,lockB),"t1").start();
        new Thread(new MyThread(lockB,lockA),"t2").start();
    }
}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);
            }
        }
    }
}

jps locates the process number to see which process has a problem


g var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}

2. Self designed spin lock

```java
public class SpinlockDemo {

    // default
    // int 0
    //thread null
    AtomicReference<Thread> atomicReference=new AtomicReference<>();

    //Lock
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.currentThread().getName()+"===> mylock");

        //Spin lock
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName()+" ==> In spin~");
        }
    }


    //Unlock
    public void myUnlock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.currentThread().getName()+"===> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }

}

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
        //ReentrantLock reentrantLock = new ReentrantLock();
        //reentrantLock.lock();
        //reentrantLock.unlock();


        //Spin lock using CAS
        SpinlockDemo spinlockDemo=new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myUnlock();
            }
        },"t1").start();

        TimeUnit.SECONDS.sleep(1);


        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myUnlock();
            }
        },"t2").start();
    }
}

The t2 process must wait for the t1 process to Unlock before it can Unlock. Before that, wait

4) Deadlock

public class DeadLock {
    public static void main(String[] args) {
        String lockA= "lockA";
        String lockB= "lockB";

        new Thread(new MyThread(lockA,lockB),"t1").start();
        new Thread(new MyThread(lockB,lockA),"t2").start();
    }
}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);
            }
        }
    }
}

jps locates the process number to see which process has a problem

Topics: Java Maven