03 - collect thread safety, lock, Callable, JUC auxiliary classes

Posted by iluv8250 on Sun, 02 Jan 2022 01:17:17 +0100

catalogue

1. List collection thread safety

2.HashSet set thread safety

3.HashMap collection thread safety

4. Eight questions

5. Fair lock and unfair lock

6. Re entrant lock

7. Deadlock

8.Callable interface

FutureTask

9.JUC auxiliary class

9.1 CountDownLatch decrease count

9.2 CyclicBarrier

9.3 Semaphore semaphore

1. List collection thread safety

The list collection is thread unsafe. When multiple threads modify the list, exceptions will occur, as shown in the following code

/**
 * List Collection thread unsafe
 */
public class Test08 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        //Multithreading, adding elements
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                //add to
                list.add(UUID.randomUUID().toString().substring(0,8));
                //Get content ConcurrentModificationException
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

1.1 Vector: less used

List<String> list = new Vector<>();

Vector uses the Synchronized keyword to solve the concurrency problem, which is not efficient

1.2 Collections tool class, less used

List<String> list = Collections.synchronizedList(new ArrayList<>());

1.3 JUC solution, common

List<String> list = new CopyOnWriteArrayList<>();

Underlying principle of CopyOnWriteArrayList:

Copy on write technology:

For a data, concurrent reading of its contents is supported during reading. When writing, it is written independently (first copy a set that is the same as the original set, write new content, and merge the new content with the previous set), and then read is to read the content in the new set.

It is explained in the JDK help document:

It is a variant of ArrayList thread safe version. A series of operations are completed by making a new copy of the underlying array. This is usually expensive. When the number of traversal operations far exceeds the modification, it may be more effective than the alternative method. It is useful when you can't or don't want to synchronize traversal, but you need to eliminate the interference between concurrent threads.

Its add method source code:

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //1. Array copy
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //2. Adding new elements
            newElements[len] = e;
            //3. Consolidation
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

2.HashSet set thread safety

Set<String> set = new HashSet<>();

Solution:

Set<String> set = new CopyOnWriteArraySet<>();

3.HashMap collection thread safety

Map<String, String> map = new HashMap<>();

Solution:

Map<String, String> map = new ConcurrentHashMap<>();

4. Eight questions

1. Output SMS first and then email

class Phone{
    public synchronized void sendSMS() throws Exception{
       System.out.println("-------sendSMS");
    }

    public synchronized void sendEmail() throws Exception{
        System.out.println("------sendEmail");
    }

    public void getHello(){
        System.out.println("------getHello");
    }
}

public class Test09 {
    public static void main(String[] args) throws InterruptedException {
        Phone p1 = new Phone();
       
        new Thread(()->{
            try {
                p1.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"AA").start();

        //Here, sleep will lead to the final result, which must be SMS before Email
        Thread.sleep(100);

        new Thread(()->{
            try {
                p1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"BB").start();
    }

}

2. Stay in SMS for 4 seconds, still SMS first and then Email

class Phone{
    public synchronized void sendSMS() throws Exception{
        //Stay for 4 seconds
        TimeUnit.SECONDS.sleep(4);

        System.out.println("-------sendSMS");
    }

    public synchronized void sendEmail() throws Exception{
        System.out.println("------sendEmail");
    }

    public void getHello(){
        System.out.println("------getHello");
    }
}

public class Test09 {
    public static void main(String[] args) throws InterruptedException {
        Phone p1 = new Phone();
        //Phone p2 = new Phone();

        new Thread(()->{
            try {
                p1.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                p1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"BB").start();
    }

}

3. Hello before SMS

When the method in thread BB is changed to getHello, hello will be output first, and SMS will output after waiting for 4 seconds.  

p1.getHello();

4. Now there are 2 mobile phones, Email first and EMS later

public static void main(String[] args) throws InterruptedException {
        Phone p1 = new Phone();
        Phone p2 = new Phone();

        new Thread(()->{
            try {
                p1.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                p2.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"BB").start();
    }

5. One mobile phone, two static methods, output SMS and Email

class Phone{
    public static synchronized void sendSMS() throws Exception{
        //Stay for 4 seconds
        TimeUnit.SECONDS.sleep(4);

        System.out.println("-------sendSMS");
    }

    public static synchronized void sendEmail() throws Exception{
        System.out.println("------sendEmail");
    }

    public void getHello(){
        System.out.println("------getHello");
    }
}

public class Test09 {
    public static void main(String[] args) throws InterruptedException {
        Phone p1 = new Phone();
        Phone p2 = new Phone();

        new Thread(()->{
            try {
                p1.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try {
                p1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"BB").start();
    }

}

 

5. Fair lock and unfair lock

Fair lock: multiple threads obtain locks in the order of applying for locks. Threads will directly enter the queue to queue. They will always be the first in the queue to obtain locks.

  • Advantages: all threads can get resources and will not starve to death in the queue.
  • Disadvantages: the throughput will drop a lot. Except for the first thread in the queue, other threads will block, and the cost of cpu waking up blocked threads will be great.

Unfair lock: when multiple threads acquire a lock, they will directly try to obtain it. If they cannot obtain it, they will enter the waiting queue. If they can obtain it, they will directly obtain the lock.

  • Advantages: it can reduce the overhead of CPU wake-up threads, and the overall throughput efficiency will be high. The CPU does not have to wake up all threads, which will reduce the number of wake-up threads.
  • Disadvantages: you may also find that this may cause the thread in the middle of the queue to fail to obtain the lock all the time or fail to obtain the lock for a long time, resulting in starvation.
//true means fair lock
private final ReentrantLock reentrantLock = new ReentrantLock(true);
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() && //Is someone here?
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { //People line up
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

6. Re entrant lock

Both synchronized (implicit) and lock (explicit) are reentrant locks

Implicit: the locking and unlocking process is completed automatically

synchronized reentrant locks, also known as recursive locks, can be called recursively

7. Deadlock

Deadlock: a phenomenon in which two or more processes wait for each other because they compete for resources during execution. If there is no external interference, they can no longer be executed.

Causes of Deadlock: 1 Insufficient system resources, 2 The process running sequence is inappropriate, 3 Improper allocation of resources

Handwritten simple deadlock Code:

/**
 * DeadLock demonstration
 */
public class Test10 {
    static Object a = new Object();
    static Object b = new Object();


    public static void main(String[] args) {
        new Thread(()->{
            synchronized(a){
                System.out.println("Hold lock A:"+a);
                synchronized (b){
                    System.out.println("Acquire lock b");
                }
            }
        },"AA").start();

        new Thread(()->{
            synchronized(b){
                System.out.println("Hold lock B:"+b);
                synchronized (a){
                    System.out.println("Acquire lock a");
                }
            }
        },"BB").start();
    }
}

Deadlock verification method:

Open the console

Enter jps to find the process number of the current class, and enter jstack to query the stack information

Check stack information

 

8.Callable interface

The difference between Runnable and Callable:

1. The method specified by Callable is call(), and the method specified by runnable is run()
2. Callable tasks can return values after execution, while Runnable tasks cannot return values
3. The call method can throw exceptions, but the run method cannot
4. Running the Callable task can get a Future object that represents the result of asynchronous calculation. It provides a method to check whether the calculation is completed, wait for the calculation to complete, and retrieve the results of the calculation. Through the Future object, you can understand the task execution, cancel the task execution, and obtain the execution results.

FutureTask

Easy to use:

public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            return 1024;
        });
        new Thread(futureTask,"aa thread ").start();
        System.out.println("Value:"+futureTask.get());
        System.out.println("main end");
    }

9.JUC auxiliary class

9.1 CountDownLatch decrease count

After six students left the classroom one after another, the monitor locked the door.

/**
 * Show me CountDownLatchDemo
 */
public class Test12 {
    //After six students left the classroom one after another, the monitor locked the door
    public static void main(String[] args) {

        //Create 6 threads
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "Left the classroom");
            },String.valueOf(i)).start();
        }

        //Output shift leader locks the door
        System.out.println(Thread.currentThread().getName() + ": The door is locked");
    }
}

But there are problems:

The monitor didn't wait for all six students to leave, but at least one student locked it before going out.

solve:

//After six students left the teacher one after another, the monitor locked the door
    public static void main(String[] args) throws InterruptedException {

        //CountDownLatch counter
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //Create 6 threads
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "Left the classroom");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName() + ": The door is locked");

    }

 

9.2 CyclicBarrier

Introduction: it can block a group of threads until an event occurs, and it can block a group of threads until an event occurs. CyclicBarrier: it can make a certain number of threads gather repeatedly at the fence position. When the thread reaches the fence position, the await method will be called, which will block until all threads reach the fence position. If all threads reach the fence position, the fence will open, all threads will be released, and the fence will be reset for next use.

Construction method:

The default construction method of CyclicBarrier is CyclicBarrier(int parties). Its parameter indicates the number of threads intercepted by the barrier. Each thread uses the await() method to tell CyclicBarrier that I have reached the barrier, and then the current thread is blocked.

Another constructor of CyclicBarrier, CyclicBarrier(int parties, Runnable barrierAction), is used to give priority to the execution of barrierAction when a thread reaches the barrier, so as to facilitate the processing of more complex business scenarios.

await method:

The thread calling the await method tells the CyclicBarrier that it has reached the synchronization point, and then the current thread is blocked

Code demonstration:

/**
 * Demonstrate CyclicBarrier
 */
public class Test13 {

    //Create fixed value
    private static final int NUMBER = 7;

    //Collect seven dragon balls to summon the Dragon
    public static void main(String[] args) {
        //Create CyclicBarrier
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
            System.out.println("Collect 7 dragon balls to summon the dragon");
        });

        //Process of collecting 7 dragon balls
        for (int i = 1; i <= 7; i++) {
            new Thread(()->{
                try{
                    System.out.println(Thread.currentThread().getName() + "The star dragon balls have been collected");
                    //wait for
                    cyclicBarrier.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();

        }

    }
}

The difference between CyclicBarrier and CountDownLatch:

The counter of CountDownLatch can only be used once, while the counter of CyclicBarrier can be reset with the reset() method and can be used multiple times, so CyclicBarrier can handle more complex scenes;

CyclicBarrier also provides some other useful methods. For example, getNumberWaiting() method can obtain the number of threads blocked by CyclicBarrier, and isBroken() method is used to know whether the blocked thread is interrupted;

CountDownLatch allows one or more threads to wait for the generation of a set of events, while CyclicBarrier is used to wait for other threads to run to the fence

9.3 Semaphore semaphore

Introduction:

Semaphore. Conceptually, semaphores maintain a set of licenses, which can also be understood as the use rights of a set of resources. We can use the acquire method to obtain the license, return the license, and use the release method. Semaphores are often used to limit the number of threads for certain resources. For example, limit the concurrency of a web interface.

The process of semaphore releasing resources:

The application layer calls the release method to release resources

Tryrereleaseshared attempted to free the resource

If the resource is released successfully, wake up the next node

Code demonstration:

/**
 * Demo Semaphore
 */
public class Test14 {

    //6 cars, 3 parking spaces
    public static void main(String[] args) {
        //Create semaphore
        Semaphore semaphore = new Semaphore(3);

        //6 vehicles
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                try{
                    //obtain
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"Grabbed the parking space");

                    //Simulated parking time
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));

                    System.out.println(Thread.currentThread().getName()+"--Left the parking space");


                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    //release
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }

    }
}

 

Topics: Java Concurrent Programming security elementUI