Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

JUC

JUC is short for the Java.util.Concurrent toolkit. This is a toolkit for handling threads.

And the package contains many concurrent programming needs to use the class.

JUC has been around since JDK 1.5.

JUC is designed to better support high-concurrency tasks

Concurrency: Can be understood as multiple threads operating on the same resource

1. Traditional Synchronized locks

Do not add the Synchronize the lock

Take selling tickets:

package com.cheng.lock;


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

        Ticket ticket = new Ticket();
        // Concurrency: Put the resource class in each thread, multiple threads operate on the same resource lambda expression: (parameter)->{code}
        new Thread(()-> {
            for (int i = 0; i < 50; i++) { ticket.sale(); }},"Xiao Ming").start();
        new Thread(()-> {
            for (int i = 0; i < 50; i++) { ticket.sale(); }},"Little red").start();
        new Thread(()-> {
            for (int i = 0; i < 50; i++) { ticket.sale(); }},"White").start(); }}Resource class Ticket, attribute + method
class Ticket{
   private int nums = 50;

    public void sale(a){
        if (nums > 0){
            System.out.println(Thread.currentThread().getName()+"Sold the first."+(nums--)+"One ticket, the rest."+nums+"Ticket"); }}}Copy the code

Run to view the result:

When a resource is operated without a Synchronize lock, data is corrupted when multiple threads operate the resource.

Combined with the Synchronize lock

Add a Synchronize lock to the method

public synchronized void sale(a){
    if (nums > 0){
        System.out.println(Thread.currentThread().getName()+"Sold the first."+(nums--)+"One ticket, the rest."+nums+"Ticket"); }}Copy the code

After running, the data is in order without disorder.

2, the LOCK LOCK

Lock is an interface that controls multiple threads’ access to a shared resource under the JUC package. Only one thread can hold the Lock at a time.

The thread should acquire the Lock object before starting to access the shared resource.

Lock implementation class

ReentrantLock: ReentrantLock, exclusive lock, lock and unlock process needs to be manually, difficult to operate, but very flexible

ReentrantReadWriteLock. ReadLock: read lock

ReentrantReadWriteLock. WriteLock: write locks

ReentrantLock implements fair and unfair locks

Fair and unfair locks:

A fair lock means that when a lock is available, the thread that has waited the longest on the lock gains access to it, first come, second come.

Non-fair lock is randomly assigned this right to use, can jump the queue.

Already the source code:

According to the above source code analysis:

The default ReentrantLock implementation is unfair locking (because unfair locking performs better)

When creating a ReentrantLock, pass true to create a fair lock, pass false or pass no argument to create an unfair lock

LOCK LOCK example

1、new ReentrantLock();

2, lock lock. Lock ()

3, Unlock lock.unlock()

package com.cheng.lock;

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

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

        Ticket ticket = new Ticket();
        // Concurrency: Put the resource class in each thread, multiple threads operate on the same resource lambda expression: (parameter)->{code}
        new Thread(()-> { for (int i = 0; i < 20; i++) ticket.sale(); },"Xiao Ming").start();
        new Thread(()-> { for (int i = 0; i < 20; i++) ticket.sale(); },"Little red").start();
        new Thread(()-> { for (int i = 0; i < 20; i++) ticket.sale(); },"White").start(); }}Resource class Ticket, attribute + method
class  Ticket1{
   private int nums = 20;

    // Create a ReentrantLock object
    Lock lock =  new ReentrantLock();

    public void sale(a){
        lock.lock(); / / lock
        try {
            // Business code
            if (nums > 0){
                System.out.println(Thread.currentThread().getName()+"Sold the first."+(nums--)+"One ticket, the rest."+nums+"Ticket"); }}catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock(); / / unlock}}}Copy the code

3. The difference between Synchronized and Lock

1. Synchronized is a built-in Java keyword. On the JVM level, Lock is a Java class.

2. Synchronized cannot determine whether the Lock is obtained. Lock can determine whether the Lock is obtained.

3. Synchronized will automatically release the Lock when an abnormal thread occurs, so no abnormal deadlock will occur. Lock must be manually released in finally;

Synchronized: if thread A blocks, thread B will wait and Lock will not wait. TryLock () is used to try to acquire the Lock. If it cannot obtain the Lock, the thread can stop waiting.

5. Synchronized locks are reentrant, uninterruptible, and non-fair, while synchronized locks are reentrant, judge, and fair.

6.Lock Lock is suitable for a large number of synchronized code synchronization problems, synchronized Lock is suitable for a small number of code synchronization problems.

7. In terms of performance, if the resource competition is not fierce, the performance of the two is similar, but when the resource competition is very fierce (that is, a large number of threads are competing at the same time), the performance of Lock is far better than synchronized.

4. Producer-consumer issues

4.1 Synchronized producer and consumer issues

package com.cheng.PC;


public class PCSynchronize {
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        // Create two threads to manipulate resources
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    test1.increment();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    test1.decrement();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"B").start(); }}class Test1{
    private int nums = 0;

    public synchronized void increment(a) throws InterruptedException {
        if(nums ! =0) {this.wait();// The thread waits
        }
        nums++;
        System.out.println(Thread.currentThread().getName()+"-- >"+nums);
        this.notifyAll();// after nums++, wake up other threads to num--
    }

    public synchronized void decrement(a) throws InterruptedException {
        if (nums == 0) {this.wait();
        }
        nums--;
        System.out.println(Thread.currentThread().getName()+"-- >"+nums);
        this.notifyAll();//nums-- after the notification wakes up other threads to num++}}Copy the code

Start test:

The above code looks fine, but what if we increased the number of threads from two to four?

Add two threads C,D:

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

Start the test again:

Communication between threads is problematic because we use if to determine if we need to wait, which creates a false wake up, and waiting should always occur in the loop.

False wake up: A thread can be woken up without being notified, interrupted, or timed out.

Fix false wake up: change the above if to while:

public synchronized void increment(a) throws InterruptedException {
    while(nums ! =0) {this.wait();// The thread waits
    }
    nums++;
    System.out.println(Thread.currentThread().getName()+"-- >"+nums);
    this.notifyAll();// after nums++, wake up other threads to num--
}

public synchronized void decrement(a) throws InterruptedException {
    while (nums == 0) {this.wait();
    }
    nums--;
    System.out.println(Thread.currentThread().getName()+"-- >"+nums);
    this.notifyAll();//nums-- after the notification wakes up other threads to num++
}
Copy the code

Start the test again: OK!

4.2 Lock version producer consumer issues

Contition is introduced

Synchronized is used in conjunction with wait()/notify() to implement wait/notification mode. The Lock object’s newContition() method returns an instance of Condition. The Condition class also implements wait/notification mode.

Condition commonly used methods:

  • Await () : causes the current thread to wait and releases the lock. When another thread calls signal() or signalAll(), the thread regains the lock and continues.

  • Signal () : wakes up a waiting thread. If any thread is waiting for this condition, a thread is selected to wake up and that thread must reacquire the lock before receiving await.

  • SignalAll () : Wakes up all waiting threads. If any thread is waiting for this condition, then they are woken up and each thread must reacquire the lock before receiving from await.

Condition Notifies and waits

Code test:

package com.cheng.PC;

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

public class PCLock {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    test2.increment();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    test2.decrement();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    test2.increment();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    test2.decrement();
                } catch(InterruptedException e) { e.printStackTrace(); }}},"D").start(); }}class Test2{
    private int nums = 0;
    Lock lock = new ReentrantLock();// Get the lock object
    Condition condition = lock.newCondition();// Get the Condtion object

    public void increment(a) throws InterruptedException {
        lock.lock();
        try {
            while(nums ! =0) {// The thread waits
                condition.await();
            }
            nums++;
            System.out.println(Thread.currentThread().getName()+"-- >"+nums);
            // after nums++, wake up other threads to num--
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}public void decrement(a) throws InterruptedException {
        lock.lock();
        try {
            while (nums == 0){
                condition.await();
            }
            nums--;
            System.out.println(Thread.currentThread().getName()+"-- >"+nums);
            condition.signalAll();//nums-- after the notification wakes up other threads to num++
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}}Copy the code

Result: Wake up randomly, execution order is out of order

With notify(), the JVM wakes up a waiting thread at random, and the Condition class allows selective notification.

Condition implements selective notification

Multiple Condition implements partial thread notification, which is more flexible to use,

Create three threads A, B, and C in the sequence A->B->C

package com.cheng.PC;

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

public class PCLockPlus {
    public static void main(String[] args) {
        Test3 test3 = new Test3();

        // Create three threads A, B, and C in the order A->B->C
        new Thread(() -> {
            for (int i = 0; i < 10; i++) { test3.TestA(); }},"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) { test3.TestB(); }},"B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) { test3.TestC(); }},"C").start(); }}class Test3 {
    private Lock lock = new ReentrantLock();
    // Create three Condition instances, each monitoring a thread
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int nums = 1; //nums=1, execute thread A; Nums =2, execute thread B; Nums =3, execute thread C


    public void TestA(a) {
        lock.lock();
        try {
            while(nums ! =1) {
                condition1.await();
            }
            nums = 2;
            System.out.println(Thread.currentThread().getName() + "->AAAAA");
            condition2.signal();Condition2 when executed
        } catch (Exception e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}public void TestB(a) {
        lock.lock();
        try {
            while(nums ! =2) {
                condition2.await();
            }
            nums = 3;
            System.out.println(Thread.currentThread().getName() + "->BBBBB");
            condition3.signal();// specify condition3
        } catch (Exception e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}public void TestC(a) {
        lock.lock();
        try {
            while(nums ! =3) {
                condition3.await();
            }
            nums = 1;
            System.out.println(Thread.currentThread().getName() + "->CCCCC");
            condition1.signal();// specify condition1
        } catch (Exception e) {
            e.printStackTrace();
        } finally{ lock.unlock(); }}}Copy the code