JUC concurrent programming (1) Thread, TreadPoolExecutor, BlockingQueue, Synchronized, Lock, JUC auxiliary classes

Thread

How to create multithreaded

Inherit Thread

  1. Method 1: Inherit the Thread class, and create steps:
    • Define the subclass MyThread to inherit Thread and rewrite the run() method
    • Create an object of the MyThread class
    • Call the start() method of the thread object to start the thread (the run() method is executed after startup)

Because this creation method has already inherited the Thread class, it cannot inherit other classes, which is not conducive to expansion.

  1. Method 2: Declare a class that implements the Runnable interface.
    • Definition Define a thread task class MyRunnable to implement the Runnable interface and override the run() method.
    • Create the MyRunnable object
    • Hand over the MyRunnable object to Thread for processing
    • Call the start method of the Thread object to start

Implement the runnable interface

The second method only implements the interface, you can continue to inherit the class and implement the interface, and the scalability is stronger. But the disadvantage is that there is an extra layer of packaging for programming (the runnable object needs to be passed to the thread to construct the thread object)
insert image description here
Method 1 implementation:

public class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 5; ++i) {
    
    
            System.out.println("子线程执行输出:" + i);
        }
    }
}
 Thread t1 = new MyThread();
 t1.start();

Method two implementation:

Thread t = new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(i);
        }
    }
});
for (int i = 0; i < 10; i++) {
    
    
    System.out.println("主线程:" + i);
}

Of course, you can use lambda expressions for shorthand.

 Thread t = new Thread(() -> {
    
    
     for (int i = 0; i < 10; i++) {
    
    
         System.out.println( "子线程" + i);
     }
 });
 for (int i = 0; i < 10; i++) {
    
    
     System.out.println("主线程:" + i);
 }
  1. Method 3
    There is a problem in the first two creation methods:
    • None of their rewritten run() methods can return results directly
    • Not suitable for business scenarios that need to return thread execution results

Define a class that implements the Callable interface and assemble it into a FutureTask

jdk5 uses Callable and FutureTask interfaces to realize the above functions.

Create steps:

  • Define the class to implement the Callable interface, rewrite the call method, and encapsulate the things to be done.
  • Use FutureTask to encapsulate the Callable object into a thread task object.
  • Hand over the thread task object to Thread for processing
  • Call the start method of Thread to start the thread and execute the task.
  • After the thread is executed, use the get() method of FutureTask to obtain the result of the task execution.

Implementation of method three:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo3 {
    
    
    public static void main(String[] args) {
    
    
        Callable<String> call1 = new MyCallable(100000);
        FutureTask<String> f1 = new FutureTask<>(call1);
        Thread t1 = new Thread(f1);
        t1.start();

        Callable<String> call2 = new MyCallable(100000);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
    
    
            // 如果f1没有执行完毕,那么get这里会等待,直至完成
            String r1 =  f1.get();
            System.out.println("第一个结果:" + r1);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
            // 如果f2没有执行完毕,那么get这里会等待,直至完成
        try {
    
    
            String r2 =  f2.get();
            System.out.println("第二个结果:" + r2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {
    
    
    private int n;
    public MyCallable (int n) {
    
    
        this.n = n;
    }
    @Override
    public String call() throws Exception {
    
    
        int sum = 0;
        for (int i = 0; i <= n; i++) {
    
    
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

Common APIs

insert image description here
insert image description here

Native API

sleep

When a thread's sleep method is called, causes the currently executing thread to sleep (temporarily halt execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. If any thread interrupts the current thread, an InterruptedException will be thrown to clear the interrupt status of the current thread.

interrupt

  • When a thread calls interrupt(), if the thread is in a normal active state, then the thread's interrupt flag will be set to true, nothing more, the thread with the interrupt flag set will continue to run normally, unaffected. That is, interrupt() only sets the interrupt flag, but does not actually interrupt the thread, and requires the cooperation of the called thread. It's like you tell a person to shut up, but in the end whether that person shuts up or not depends on its cooperation.
  • If the thread is in a blocked state (such as sleep, wait, join, etc.), call the interrupt () method of the current thread object in another thread, then the thread will immediately exit the blocked state, and the interrupt status flag will be cleared. And throw an InterruptedException exception.
  • For inactive threads, calling interrupt() has no effect.

join

The join method allows one thread to execute before joining another thread. During the execution of this thread, other threads enter the blocking state. Of course, you can also specify the join input parameter (specify the timeout period for execution waiting), and wait for a few milliseconds at most for the thread to terminate. A timeout of 0 means wait forever.

public class demo1 {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
                System.out.println("+++++++++++++");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        t1.start();
        try {
    
    
            t1.join();
            System.out.println("ok");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

yield

The literal meaning of yield is to give in. Calling this method indicates to the scheduler that the current thread is willing to relinquish its current use of the processor, which the scheduler is free to ignore.

yield is a heuristic attempt to improve the relative progress between threads that would otherwise overuse the CPU. There are usually two usage scenarios when using the yield method:

  • The use of yield should be coupled with detailed analysis and benchmarking to ensure that it actually has the desired effect, but this approach is rarely used. May be useful for debugging or testing purposes, it may help reproduce bugs due to race conditions
  • It may also be useful when designing concurrency control structures such as those in the java.util.concurrent.locks package
public class TestYield {
    
    
    public static void main(String[] args) {
    
    
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");
        thread1.start();
        thread2.start();
    }
 
    private static class MyThread extends Thread {
    
    
        public MyThread(String name) {
    
    
            super(name);
        }
        @Override
        public void run() {
    
    
            for (int i = 1; i <= 5; i++) {
    
    
                if (i % 2 == 0) {
    
    
                    Thread.yield();
                    System.out.println(getName() + ":" + i);
                }
            }
        }
    }
}

TreadPoolExecutor

Thread pool is a technology that can reuse threads. Because the overhead of creating new threads is very high, using the thread pool can reuse threads and improve program performance.
insert image description here

Get the thread pool object

Since JDK5.0, an interface representing the thread pool has been provided:
How does ExecutorService obtain the thread pool object?

  • Method 1: Use the implementation class ThreadPoolExecutor of ExecutorService to self-create a thread pool object. This method is the most flexible.

  • Method 2: Use Executors (a tool class for thread pools) to call methods to return thread pool objects with different characteristics.

Executors built-in thread pool

1. newCachedThreadPool
creates a cacheable thread pool. If the length of the thread pool exceeds the processing needs, it can flexibly recycle idle threads. If there is no recyclable thread, a new thread will be created. The characteristics of this type of thread pool are:
there is almost no limit to the number of worker threads created (in fact, there is a limit, the number is Integer. MAX_VALUE), so that threads can be added to the thread pool flexibly.

If no task is submitted to the thread pool for a long time, that is, if the worker thread is idle for a specified time (1 minute by default), the worker thread will be automatically terminated. After termination, if you submit a new task, the thread pool will recreate a worker thread. When using CachedThreadPool, you must pay attention to controlling the number of tasks, otherwise, due to a large number of threads running at the same time, it is likely to cause system OOM.

2. newFixedThreadPool
creates a thread pool with a specified number of worker threads. A worker thread is created whenever a task is submitted, and if the number of worker threads reaches the initial maximum number of thread pools, the submitted task is stored in the pool queue.

FixedThreadPool is a typical and excellent thread pool, which has the advantages of improving program efficiency and saving the overhead of creating threads. However, when the thread pool is idle, that is, when there are no runnable tasks in the thread pool, it will not release the worker thread, and it will also occupy certain system resources.

3. newSingleThreadExecutor
creates a single-threaded Executor, that is, only creates a unique worker thread to execute tasks, and it only uses the only worker thread to execute tasks, ensuring that all tasks are executed in the specified order (FIFO, LIFO, priority) . If this thread ends abnormally, another one will take its place, guaranteeing sequential execution. The biggest feature of a single worker thread is that tasks are guaranteed to be executed sequentially, and no multiple threads will be active at any given time.
4. newScheduleThreadPool
creates a fixed-length thread pool, and supports timing and periodic task execution, and supports timing and periodic task execution.

ThreadPoolExecutor

insert image description here
When are temporary threads created?

  • When a new task is submitted, the core threads are busy, the task queue is also full, and temporary threads can also be created, and only then will temporary threads be created.
    When do you start rejecting tasks?
  • Both the core thread and the temporary thread are busy, and the task queue is also full, and the task will be rejected when a new task comes.

insert image description here

Thread pool handles runnable tasks

        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        Runnable target = () -> {
    
    
            try {
    
    
                Thread.sleep(100000);
                System.out.println(Thread.currentThread().getName() + "输出");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        };
        // 三个核心线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 五个在等待队列
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 等待队列满了,新加两个临时线程
        pool.execute(target);
        pool.execute(target);
        
        // 拒绝任务,抛出异常
        pool.execute(target);

    }

Thread pool handles callable tasks

insert image description here
execute executes runnable tasks, and submit handles callable tasks.

BlockingQueue blocking queue

BlockingQueue is a FIFO (first-in, first-out) queue, which solves the problem of how to "transmit" data efficiently and safely in multi-threading.
insert image description here
Blocking queues have four APIs for adding and removing elements:

  • non-blocking, returns boolean
    • An exception will be thrown: add(), remove(), element()
    • No exception will be thrown: offer(), poll(), peek()
  • block
    • Always blocked: put(), take()
    • You can set the waiting time, return when timeout: offer(e, timeout, unit), poll(timeout, unit)

insert image description here

LinkedBlockingQueue linked list blocking queue

Doubly linked list blocking queue.

For FixedThreadPool and SingleThreadExector, the blocking queue they use is a LinkedBlockingQueue with a capacity of Integer.MAX_VALUE, which can be considered as an unbounded queue.

Since
the number of threads in the FixedThreadPool thread pool is fixed, there is no way to add a particularly large number of threads to process tasks. At this time, a blocking queue with no capacity limit such as LinkedBlockingQueue is needed to store tasks.

It should be noted here that since the task queue of the thread pool will never be full, the thread pool will only create threads with the number of core threads, so the maximum number of threads at this time is meaningless to the thread pool, because it will not trigger the generation of multiple threads. Threads based on the number of core threads.

SynchronousQueue synchronous queue

The SynchronousQueue synchronous queue does not store elements, as long as an element is put into it, an element needs to be taken out and taken.

The corresponding thread pool is CachedThreadPool. The maximum number of threads in the thread pool CachedThreadPool is the maximum value of Integer. It can be understood that the number of threads can be expanded infinitely.
CachedThreadPool is just the opposite of the previous thread pool, FixedThreadPool. In the case of FixedThreadPool, the capacity of the blocking queue is unlimited. Here, the number of threads in CachedThreadPool can be expanded infinitely, so the CachedThreadPool thread pool does not need a task queue to store tasks, because Once a task is submitted, it is directly forwarded to the thread or creates a new thread to execute without saving them separately.

SynchronousQueue<String> bq = new SynchronousQueue();
new Thread(() -> {
    
    
    try {
    
    
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
}, "A").start();

new Thread(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
        bq.take();
        System.out.println(Thread.currentThread().getName());
        bq.take();
        System.out.println(Thread.currentThread().getName());
        bq.take();
        System.out.println(Thread.currentThread().getName());
    } catch (Exception e){
    
    
        e.printStackTrace();
    }
}, "B").start();
}

DelayedWorkQueue delayed blocking queue

The third blocking queue is DelayedWorkQueue. Its corresponding thread pools are ScheduledThreadPool and SingleThreadScheduledExecutor. The biggest feature of these two thread pools is that they can delay the execution of tasks, such as executing tasks after a certain period of time or executing tasks at regular intervals. .

The characteristic of DelayedWorkQueue is that the internal elements are not sorted according to the time when they are put in, but the tasks are sorted according to the length of delay, and the internal use is a "heap" data structure. The reason why the thread pool ScheduledThreadPool and SingleThreadScheduledExecutor choose DelayedWorkQueue is because they themselves execute tasks based on time, and the delay queue can just sort tasks by time to facilitate task execution.

Synchronized

1、并发就是多线程操作同一个资源。

2、在Java中,线程就是一个单独的资源类,没有任何附属操作。资源中包含并发操作的属性、方法。

Concurrency case - an example of multi-threaded ticket buying:
In my example, the public resource class is Ticket, left is the remaining number of tickets, cnt is the recorded number of sold tickets, and the sale() method prints who bought it when there are surplus tickets One ticket, and display the current remaining tickets and the total sold tickets.

class Ticket {
    
    
    int left;
    int cnt = 0;
    public int getLeft() {
    
    
        return left;
    }

    public void setLeft(int left) {
    
    
        this.left = left;
    }

    public Ticket(int n) {
    
    
        this.left = n;
    }
    public void sale() {
    
    
        try {
    
    
            Thread.sleep(100); // 模拟卖票耗时
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        if (left > 0) {
    
    
            ++cnt;
            --left;
            System.out.println("线程:" + Thread.currentThread().getName() + "剩余:" + left + ",共卖出:" + cnt);
        }
    }
}

In the main function, set the number of votes, the number of people who buy tickets, and the number of attempts for each person to buy tickets:

public static void main(String[] args) {
    
    
    int ticketNum = 8, people = 4, chance = 5;
    Ticket t = new Ticket(ticketNum);
    System.out.println("开售前:---------" + t.getLeft());
    for (int i = 0; i < people; ++i) {
    
    
        new Thread(() -> {
    
    
            for (int j = 0; j < chance; ++j) {
    
    
                t.sale();
            }
        }, Integer.toString(i)).start();
    }
    try {
    
    
        Thread.sleep(3000);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
}

result:
insert image description here

synchronized solution

The basis of synchronized implementation of synchronization: every object in Java can be used as a lock. Specifically, it takes the following three forms:
1. 对于普通同步方法,锁是当前实例对象。
2. 对于静态同步方法,锁是当前类的Class对象。
3. 对于同步方法块,锁是Synchonized括号里配置的对象。

Let sale() become a synchronous method directly, that is, when each thread accesses the sale() method, it will lock the object of the instance (that is, the public resource) where the current method is located, then only one thread can operate at a time, and other threads must wait:
insert image description here
insert image description here

Lock lock

The lock uses three steps:

  1. Create a lock: Lock lk = new ReentrantLock();
  2. Get a lock: lk.lock
  3. try - catch - finally, where the business logic that requires concurrency control is written in try, and the lock is released in finally to ensure that the lock is released normally when an exception occurs. lk.lock().
class Ticket2 {
    
    
    int left;
    int cnt = 0;
    Lock lk = new ReentrantLock();
    public int getLeft() {
    
    
        return left;
    }

    public void setLeft(int left) {
    
    
        this.left = left;
    }

    public Ticket2(int left) {
    
    
        this.left = left;
    }

    public void sale() {
    
    
        lk.lock();
        try {
    
    
            if (left > 0) {
    
    
                ++cnt;
                --left;
                System.out.println("线程:" + Thread.currentThread().getName() + "剩余:" + left + ",共卖出:" + cnt);
            }
        }
        catch (Exception e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            lk.unlock();
        }
    }
}

ReentrantLock can also guarantee concurrency safety.
insert image description here

The difference between synchronized lock and Lock lock

  1. Synchronized is a Java built-in keyword and Lock is a Java class.
  2. Synchronized cannot acquire the state of the lock, and Lock can determine whether the lock has been acquired.
  3. Synchronized will automatically release the lock, and the lock must be locked manually. If the lock is not released, a deadlock will occur.
  4. Synchronized, the thread that has not acquired the lock will wait forever. Lock lock, there is a mechanism to try to acquire the lock, and it does not necessarily wait forever.
  5. synchronized is reentrant and unfair; Lock lock is reentrant and fair locks can be set, that is, one is built-in keywords that cannot be modified, and the other is customized.
  6. Synchronized is suitable for locking a small amount of code synchronization issues, and Lock is suitable for locking a large number of synchronization codes.

producer consumer problem

synchronized implementation

Threads A and B operate the same variable num, A lets num + 1,
B lets num - 1, and the two are used alternately.

Here, after A has completed the operation, B needs to be notified, and B needs to be notified after the operation is completed, so as to realize thread synchronization. It is equivalent to handing over A to B for consumption after A is produced, and then notifying A after B completes consumption.

Complete this production-consumption model programming trilogy:

  • Wait: when the condition is not met, the while loop waits
  • Business: When the conditions are met, execute the business.
  • Notification: After the business is completed, notify other threads.

Build a resource class Data, which has a member variable num, build two synchronization methods, one executes +1, and the other executes -1. In the main method, start two threads A and B, and try to operate +1 and -1 respectively:

class Data {
    
    
    private int num = 0;

    public synchronized void increase() {
    
    
        while (num != 0) {
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        ++num;
        System.out.println(Thread.currentThread().getName() +":->"+ num);
        this.notifyAll();
    }

    public synchronized void decrease() {
    
    
        while (num != 1) {
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        --num;
        System.out.println(Thread.currentThread().getName() +":->"+ num);
        this.notifyAll();
    }
}

    public static void main(String[] args) {
    
    
        Data d = new Data();
        new Thread(() -> {
    
    
            for (int i = 0; i < 5; i++) {
    
    
                d.increase();
            }
        }, "A").start();

        new Thread(() -> {
    
    
            for (int i = 0; i < 5; i++) {
    
    
                d.decrease();
            }
        }, "B").start();
    }
}

lock version implementation - condition

insert image description here
You can use lock to build condition condition variables. condition provides await() method and signal() and signalAll() methods, similar to wait(), notify() and notifyAll;

Key steps:

  • 1. Create ReentrantLock() lk and get the condition of lk
  • 2. Locking: lk.lock()
  • 3、try - catch - final
    • Write business logic in try:
      • Wait: When the condition is not met, the while loop waits: condition.await();
      • Business: When the conditions are met, execute the business
      • Notification: After the business is completed, notify other threads: condition.signalAll();
    • Release the lock in final: lk.unlock();
class Data2 {
    
    
    private int num = 0;
    Lock lk = new ReentrantLock();
    Condition condition = lk.newCondition();
    
    public  void increase() {
    
    
        lk.lock();
        try {
    
    
            while (num != 0) condition.await();
            ++num;
            System.out.println(Thread.currentThread().getName() +":->"+ num);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }

    public  void decrease() {
    
    
        lk.lock();
        try {
    
    
            while (num != 1) condition.await();
            --num;
            System.out.println(Thread.currentThread().getName() +":->"+ num);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }
}

condition realizes accurate notification wake-up

insert image description here

Callable

Used for asynchronous requests with return values ​​to get results.
The first step is to build your own Callable object and implement the Callable interface. It needs a generic parameter to identify the expected result type.

class  MyCall implements Callable<Integer> {
    
    
    int a, b;

    public MyCall(int a, int b) {
    
    
        this.a = a;
        this.b = b;
    }

    @Override
    public Integer call() throws Exception {
    
    
        TimeUnit.SECONDS.sleep(2);
        return a + b;
    }
}

The Callable object is packaged with FutureTask, that is, it is packaged as a task to be executed in the future.

MyCall call = new MyCall(3, 4);
FutureTask future = new FutureTask(call);

insert image description here
The RunnableFuture composite interface is implemented in FutureTask, that is, it has the implementation of Runnable, so it can be put into Thead to start:

        new Thread(future).start();
        Integer a = 0;
        try {
    
    
            a = (Integer) future.get();
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(a.toString());

This future.get() will block!

Auxiliary classes commonly used by JUC

CountDownLatch (countdown timer)

CountDownLatch allows count threads to block in one place until the tasks of all threads are executed.

Simulate a scene, there are 6 students in the classroom, and the door can only be closed after all the students leave!

public static void main(String[] args) {
    
    
    // 1、统计num个线程的倒计时器
    int num = 6;
    CountDownLatch cn = new CountDownLatch(num);
    for (int i = 0; i < num; ++i) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "离开");
            // 2、线程结束前,倒数一下
            cn.countDown();
        }, String.valueOf(i)).start();
    }
    try {
    
    
        // 3、等待所有线程结束
        cn.await();
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    System.out.println("关门!");
}

insert image description here

CyclicBarrier (cyclic fence)

CyclicBarrier is very similar to CountDownLatch. It can also implement technical waiting between threads. What it has to do is to block a group of threads when they reach a barrier (also called a synchronization point) until the last thread reaches the barrier. Only then will the door be opened, and all threads blocked by the barrier will continue to work.

The current scenario is reversed, assuming that the number of people arriving before the teacher reaches the specified number, the door is allowed to open:

Step 1: Create a CyclicBarrier, specify the number of threads to be met and the runnable object to be executed after all threads arrive.

CyclicBarrier cb = new CyclicBarrier(num, () -> {
    
    
   System.out.println("开门!");
});

Step 2: Before the end of each thread execution, use cb.await();to wait for other threads to synchronize.

public static void main(String[] args) {
    
    
    // 1、等待的人数到达num后,才开门!
    int num = 6;
    CyclicBarrier cb = new CyclicBarrier(num, () -> {
    
    
        System.out.println("开门!");
    });
    for (int i = 0; i < num; ++i) {
    
    
        new Thread(()->{
    
    
            System.out.println(Thread.currentThread().getName() + "到达");
            try {
    
    
                // 2、线程结束前,需等待其他线程同步
                cb.await();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }, String.valueOf(i)).start();
    }
}

insert image description here

Semaphore semaphore - allows multiple threads to access it at the same time

Both synchronized and ReentrantLock allow only one thread to access a resource at a time.Semaphore(信号量)可以指定多个线程同时访问某个资源。

Semaphore has two modes: fair mode and unfair mode.

  • Fair mode: the order in which acquire is called is the order in which licenses are acquired, following FIFO
  • Unfair Mode: Preemptive

Construction method:

public Semaphore(int permits) {
    
    
        sync = new NonfairSync(permits);
    }
public Semaphore(int permits, boolean fair) {
    
    
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Both of these two construction methods must provide the number of permits. The second construction method can specify whether it is a fair mode or an unfair mode, and the default is the unfair mode.

insert image description here

The most commonly used scenario is when resources are limited, only a specified number of threads are allowed to access a certain resource at the same time, such as simulating a scene of grabbing a parking space:

Step 1:
Simulate resource situation: num parking spaces, and the user has 10
int num = 3, total = 6;
Semaphore semaphore = new Semaphore(num);

Step 2:
try -catch -final:
try: semaphore.acquire(); // Acquire the resource
finally: semaphore.release(); // Release the resource

public static void main(String[] args) {
    
    
    // 1、num个车位,而用户有total 个
    int num = 3, total = 6;
    Semaphore sm = new Semaphore(num);
    for (int i = 0; i < total; ++i) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                sm.acquire();
                System.out.println(Thread.currentThread().getName() + "抢到车位");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "离开车位");
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                sm.release();
            }
        }, String.valueOf(i)).start();
    }
}

Only 3 users can occupy the parking space at the same time.
insert image description here

ReadWriteLock read-write lock

Use the read-write lock to implement a custom cache, and only one operation is allowed when writing:

Step 1: Define a read-write lock:
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Step 2: Define a read operation function:
add a read lock before reading
readWriteLock.readLock().lock();
Release the lock after reading
Step 3: Define the write operation function ()
to add a write lock when writing, and release the lock after writing.

class MyCache {
    
    
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Map<String, Object> mp = new HashMap<>();

    public void put(String s, Object o) {
    
    
        readWriteLock.writeLock().lock();
        try {
    
    
            mp.put(s, o);
            System.out.println(Thread.currentThread().getName() + "插入:" + s);
        } catch (Exception exception) {
    
    
            exception.printStackTrace();
        } finally {
    
    
            readWriteLock.writeLock().unlock();
        }
    }

    public Object get(String s) {
    
    
        Object ans = null;
        readWriteLock.readLock().lock();
        try {
    
    
            ans = mp.get(s);
            System.out.println(Thread.currentThread().getName() + "查询:" + s);
        } catch (Exception exception) {
    
    
            exception.printStackTrace();
        } finally {
    
    
            readWriteLock.readLock().unlock();
        }
        return ans;
    }
}

Four major function interfaces

A functional interface is an interface that defines only one abstract method, or an interface annotated with @FunctionalInterface. There can be default methods.
The four functional interfaces are:

  • Functional function interface (Function): one interface input is the input of the function and the other is the output of the function
  • Consumer function interface (Consumer): the interface input is the input of the function and the return of the function is a boolean value
  • Supply Functional Interface (Supplier)
  • Predicate Functional Interface (Predicate)

function / functional

Functional interface: one interface input is the input of the function and the other is the output of the function

@FunctionalInterface
public interface Function<T, R> {
    
    

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
    // ... 两个默认函数和一个静态函数
}

assertion type

Determination interface: the interface input is the input function of the function and the return of the function is a Boolean value

public interface Predicate<T> {
    
    

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    // ...三个默认函数和一个静态函数
}

Consumption type

Consumer interface: only input, no output

@FunctionalInterface
public interface Consumer<T> {
    
    

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
    // ...一个默认方法
}

supply type

Supply interface: only output, no input

@FunctionalInterface
public interface Supplier<T> {
    
    

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
        Consumer consumer = (str) -> {
    
    
            System.out.println(str);
        };
        consumer.accept("Happy");
    }

Use case - Stream streaming programming

    /**
    有5个用户,筛选
     1、ID 必须是偶数
     2、年龄必须大于23
     3、用户名转大写字母
     4、倒序排序
     5、只需要一个用户
     **/
    public static void main(String[] args) {
    
    
        List<User> list = new ArrayList<>();
        Collections.addAll(list,
                new User(0, 22, "lzy"),
                new User(1, 20, "blzy"),
                new User(2, 25, "azy"),
                new User(3, 24, "czy"),
                new User(4, 24, "dzy"),
                new User(5, 24, "ezy"),
                new User(6, 24, "fzy"),
                new User(7, 24, "gsy"));
        list.stream().filter(e -> {
    
    return e.getId() % 2 == 1;})
                     .filter(e -> {
    
    return e.getAge() > 23;})
                     .map(e -> {
    
    return e.getUsername().toUpperCase();})
                     .sorted(Comparator.reverseOrder())
                     .limit(1)
                     .forEach(System.out::println);

    }

Guess you like

Origin blog.csdn.net/baiduwaimai/article/details/132083149