Some design patterns related to multithreading

In this section, we will focus on some multithreaded design patterns.

Singleton mode

The singleton pattern, as its name implies, is a design pattern that guarantees that only one instance of a class can be created in a program.
Instantiation of its objects is limited to creating one, not more.

In java, through

  • Represent instances using static members (uniqueness)
  • Make the construction method private (blocking the button that new creates the instance)

To achieve.
The single-case model has two modes: the hungry-man mode and the lazy-man mode.

Hungry Han Mode

Hungry Han Mode: Start the program and create an instance immediately

class Singleton{
    private static Singleton instance = new Singleton();
    
    public static Singleton getInstance(){
        return instance;
    }
    
    private Singleton(){}
}

Lazy Man Mode

Lazy mode: the program starts without creating an instance immediately; Create instances while loading classes

class SingletonLazy{
    private static SingletonLazy instance = null;
    
    public static SingletonLazy getInstance(){
        if(instance == null){
            instance = new SingletonLazy();//write
        }
        return instance;
    }
    
    private SingletonLazy(){}
}
  • For Hungry Han mode, since only multithreaded reading is involved, its threads are safe
  • For lazy mode, instances are created with multithreaded reads and writes, so threads are insecure, and subsequent threads are secure
class SingletonLazy{
    volatile private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        if(instance == null){
            synchronized (SingletonLazy.class){
                if(instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy(){}
}

if(instance == null){instance = new SingletonLazy();} This step is multi-threaded, and multiple threads may enter if judgment to create multiple instances. At this point, we need to use synchronized to restrict thread entry.

Additionally, since thread insecurity can only occur when creating instances, and synchronized is very expensive, we want to avoid synchronized as much as possible later on, so add an if(instance == null) judgment outside the code block to reduce the cost of synchronized.

Producer-consumer model

Advantages of the producer-consumer model:

  • Better "decoupling"
  • "Peak clipping and valley filling" can be achieved to improve the risk resistance of the whole system
public class Demo19 {

    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();

        Thread customer = new Thread(() -> {
            while(true){
                try {
                    int elem = queue.take();
                    System.out.println("Consumed:"+elem);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();

        Thread producer = new Thread(() -> {
            int elem = 0;
           while(true){
               System.out.println("Produced:"+elem);
               try {
                   queue.put(elem);
                   elem++;
                   Thread.sleep(800);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }

           }
        });
        producer.start();
    }
}

The above code uses a blocking queue, which comes with java. Let's simulate a blocking queue

Blocking Queue

class MyBlockingQueue{
    private int[] arr = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    public void put(int val) throws InterruptedException {
        synchronized (this){
            while(size == arr.length){//Here while needs to be judged again
                this.wait();
            }
            arr[head] = val;
            tail++;
            if(tail == arr.length){
                tail = 0;
            }
            size++;
            this.notify();
        }
    }

    public Integer take() throws InterruptedException {
        synchronized (this){
            while(size == 0){
                this.wait();
            }
            int ret = arr[head];
            head++;
            if(head == arr.length){
                head = 0;
            }
            size--;
            this.notify();
            return ret;
        }
    }
}

Tags: Java Design Pattern Singleton pattern

Posted by robdavies on Fri, 12 Aug 2022 22:10:28 +0530