Chapter 16 Common Java Multithreading Patterns

Protective pause in synchronous mode

definition

  • That is, Guarded Suspension, used when one thread waits for the execution result of another thread

Main points

  • There is a result that needs to be passed from one thread to another thread, so that they are associated with the same GuardedObject
  • If you have results that are constantly going from one thread to another then you can use a message queue (see Producer/Consumer)
  • In JDK, the implementation of join and the implementation of Future adopt this mode.
  • Because you have to wait for the result of the other party, it is classified into synchronous mode.

image-20230625105247377

GuardedObject with timeout

public class GuardedObject {
    
    
    private Object response;
    private final Object lock = new Object();
    public Object get(long time){
    
    
        //1记录最初时间
        long startTime = System.currentTimeMillis();
        //2记录还需要等待的时间
        long passedTime = 0;
        synchronized (lock){
    
    
            while (response==null){
    
    
                //4计算当前还需要等待多久时间
                long waitTime=time-passedTime;
                if (waitTime <= 0) {
    
    
                    System.out.println("break");
                    break;
                }
                try {
    
    
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //如果提前被唤醒(以为存在被虚假唤醒的情况)
               passedTime = System.currentTimeMillis() - startTime;
                System.out.println("已经过去了"+passedTime);
            }
            return response;
        }
    }
    public void complete(Object response){
    
    
        synchronized (lock){
    
    
            this.response=response;
            System.out.println("notify");
            lock.notifyAll();
        }
    }
}

test

public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
        GuardedObject v1 = new GuardedObject();
        new Thread(()->{
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
                v1.complete(null);

            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"t1").start();
        Object o = v1.get(2500);
        if(o!=null){
    
    
            System.out.println(o);
        }else {
    
    
            System.out.println("不能获取response");
        }
    }
}
notify
已经过去了1002
已经过去了2504
break
不能获取response

join principle

public final synchronized void join(long millis) throws InterruptedException {
    
    
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
    
    
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
    
    
            while (isAlive()) {
    
    
                wait(0);
            }
        } else {
    
    
            while (isAlive()) {
    
    
                long delay = millis - now;
                if (delay <= 0) {
    
    
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
  • join(0) means waiting forever, because wait(0) means waiting forever
  • The result of waiting here is actually whether the corresponding thread is alive, because we call this method using object.join()
  • delay is how long you still need to wait, now is how much time has passed.

Multitasking version of GuardedObject

image-20230625115446903

The Futures in the picture are like mailboxes on the first floor of a residential building (each mailbox has a room number), t0, t2, and t4 on the left are like residents waiting for mail, and t1, t3, and t5 on the right are like postmen.

  • If you need to use GuardedObject objects between multiple classes, it is not very convenient to pass them as parameters, so design an intermediate class for decoupling. This can not only decouple the [result waiter] and [result producer], but also Supports management of multiple tasks
public class GuardedObjects {
    
    
    // 标识 Guarded Object
    private int id;

    public GuardedObjects(int id) {
    
    
        this.id = id;
    }
    public int getId() {
    
    
        return id;
    }
    //结果
    private Object response;
    //获取结果
    //timeOut表示要等待多久
    public Object get(long timeout){
    
    
        synchronized (this){
    
    
            //1开始时间
            long startTime = System.currentTimeMillis();
            long passedTime= 0;
            while (response==null){
    
    
                long waitTime = timeout - passedTime;
                if(waitTime<=0){
    
    
                    break;
                }
                try {
    
    
                    this.wait(waitTime);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                passedTime = System.currentTimeMillis() - startTime;
            }
            return response;
        }
    }
    public void complete(Object object){
    
    
        synchronized (this){
    
    
            this.response=object;
            this.notifyAll();
        }
    }

}

Mailbox structure

public class Mailboxes {
    
    
    private static Map<Integer,GuardedObjects> boxes = new Hashtable<>();
    private static int id = 1;
    //产生唯一id
    private static synchronized int generateId(){
    
    
        return id++;
    }
    public static GuardedObjects getGuardedObject(int id) {
    
    
        return boxes.remove(id);
    }
    public static GuardedObjects createGuardedObject() {
    
    
        GuardedObjects go = new GuardedObjects(generateId());
        boxes.put(go.getId(), go);
        return go;
    }
    public static Set<Integer> getIds() {
    
    
        return boxes.keySet();
    }
}

test

class People extends Thread{
    
    
    @Override
    public void run() {
    
    
        // 收信
        GuardedObjects guardedObject = Mailboxes.createGuardedObject();
        System.out.println("开始收信 id:"+guardedObject.getId());
        Object mail = guardedObject.get(5000);
        System.out.println("收到信 id:"+guardedObject.getId() + "内容为"+ mail);
    }
}
class Postman extends Thread {
    
    
    private int id;
    private String mail;
    public Postman(int id, String mail) {
    
    
        this.id = id;
        this.mail = mail;
    }
    @Override
    public void run() {
    
    
        GuardedObjects guardedObject = Mailboxes.getGuardedObject(id);
        System.out.println("送信 id为"+id+"内容为"+mail);
        guardedObject.complete(mail);
    }
}
public class Demo2 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 3; i++) {
    
    
            new People().start();
        }
       TimeUnit.SECONDS.sleep(1);
        for (Integer id : Mailboxes.getIds()) {
    
    
            new Postman(id,"你好"+id).start();
        }
    }
}

Asynchronous model producer/consumer model

The producer-consumer pattern solves the strong coupling problem between producers and consumers through a container .
Producers and consumers do not communicate directly with each other, but communicate through blocking queues . Therefore, after the producers produce the data, they do not need to wait for the consumers to process it, but directly throw it to the blocking queue. The consumers do not ask the producers for data, but Take it directly from the blocking queue.

  • The blocking queue is equivalent to a buffer, balancing the processing capabilities of producers and consumers.

    • For example, in a "flash sale" scenario, the server may receive a large number of payment requests at the same time. If these payment requests are processed directly, the server may not be able to handle it (the processing of each payment request requires a relatively complex process). At this time, These requests can be put into a blocking queue, and then the consumer thread will slowly process each payment request. This can effectively "cut the peak" and prevent the server from being directly overwhelmed by a sudden wave of requests.
  • Blocking queues can also decouple producers and consumers.

    • For example, the whole family makes dumplings together during the Chinese New Year. There is usually a clear division of labor. For example, one person is responsible for rolling out the dumpling wrappers, and others are responsible for wrapping. The person who rolls out the dumpling wrappers is the "producer", and the person who makes the dumplings is the "consumer." The person who makes the dumpling wrappers doesn't care who the person who makes the dumplings is (as long as he can make them, whether by hand, with tools, or by machine), and the person who makes the dumplings doesn't care who the person who rolls out the dumpling wrappers does (as long as he has the dumpling wrappers, Whether rolled with a rolling pin, rolled out of cans, or bought directly from the supermarket).

    Blocking queue size

Blocking queue in the standard library

There is a blocking queue built into the Java standard library. If we need to use blocking queues in some programs, we can directly use the blocking queue in the standard library.
BlockingQueue is an interface. The real implemented classes are LinkedBlockingQueue and ArrayBlockingQueue, etc.

  • The put method is used for blocking enqueueing, and take is used for blocking dequeuing.
  • BlockingQueue also has methods such as offer, poll, peek, etc., but these methods do not have blocking characteristics.
  • Must be produced first and then consumed, otherwise it will be blocked

Determine the size of the blocking queue through the construction method.
If no number is declared, it means unbounded

Implementation of blocking queue

  • This is achieved through the "circular queue" method.
  • Use synchronized for locking control.
    • When put inserts an element, it determines if the queue is full, and waits. (Note that wait must be performed in a loop. The queue may not be full when it is awakened, because multiple threads may be awakened at the same time).
    • When take takes out elements, it determines if the queue is empty, and then waits. (It is also a loop wait)

Locking implementation

Main points

  • Different from the GuardObject in the previous protective pause, there is no one-to-one correspondence between the threads that produce results and those that consume results.
  • The consumption queue can be used to balance production and consumption thread resources
  • The producer is only responsible for generating result data and does not care how the data is processed, while the consumer concentrates on processing the result data.
  • The message queue has a capacity limit. No more data will be added when it is full, and no more data will be consumed when it is empty.
  • Various blocking queues in the JDK adopt this model.

image-20230625160404007

Simple implementation of inter-thread blocking queue

class Message {
    
    
    private int id;
    private Object message;

    public Message(int id, Object message) {
    
    
        this.id = id;
        this.message = message;
    }

    public int getId() {
    
    
        return id;
    }

    public Object getMessage() {
    
    
        return message;
    }
}
class BlockingQueue {
    
    
    private LinkedList<Message> queue;
    private int capacity;
    public BlockingQueue(int capacity){
    
    
        this.capacity=capacity;
        queue = new LinkedList<>();
    }
    public Message take(){
    
    
        synchronized (queue){
    
    
            while (queue.isEmpty()){
    
    
                System.out.println("没货了,wait");
                try {
    
    
                    queue.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            Message message = queue.removeFirst();
            System.out.println("输出一个资源");
            queue.notifyAll();
            return message;
        }
    }
    public void put(Message message){
    
    
        synchronized (queue){
    
    
            while (queue.size()==capacity){
    
    
                System.out.println("队列满了,wait");
                try {
    
    
                    TimeUnit.SECONDS.sleep(1);
                    queue.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            queue.push(message);
            System.out.println("进入一个资源");
            queue.notifyAll();
        }
    }
}
  • When put inserts an element, it determines if the queue is full, and waits. (Note that wait must be performed in a loop. The queue may not be full when it is awakened, because multiple threads may be awakened at the same time).
  • When take takes out elements, it determines if the queue is empty, and then waits. (It is also a loop wait)

test

public class BlockingQueueDemo{
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue blockingQueue = new BlockingQueue(2);
        // 4 个生产者线程
        for (int i = 0; i < 4; i++) {
    
    
            int id = i;
            new Thread(()->{
    
    
                blockingQueue.put(new Message(id,"资源"+id));
            },"t1").start();
        }
        // 1 个消费者线程, 处理结果
        new Thread(() -> {
    
    
            while (true) {
    
    
                Message message = blockingQueue.take();
                System.out.println("id为"+message.getId()+" 信息为"+message.getMessage());
            }
        }, "消费者").start();
    }
}
没货了,wait
进入一个资源
输出一个资源
进入一个资源
进入一个资源
id为2 信息为资源2
队列满了,wait
输出一个资源
id为0 信息为资源0
输出一个资源
id为3 信息为资源3
进入一个资源
输出一个资源
id为1 信息为资源1
没货了,wait

What is the role of the producer-consumer model?

This question is very theoretical, but very important:

  • 1 Improving the operating efficiency of the entire system by balancing the production capacity of producers and the consumption capacity of consumers is the most important role of the producer-consumer model
  • 2. Decoupling, which is an incidental function of the producer-consumer model. Decoupling means that there are fewer connections between producers and consumers. The fewer connections, the more they can develop independently without being restricted by each other.

Guess you like

Origin blog.csdn.net/qq_50985215/article/details/131511374