Data communication method between multiple threads

Data communication method between multiple threads

Producer consumer model

The production/consumer problem is a very typical multi-threaded problem, and the objects involved include producers, consumers, warehouses, and products. The relationship between them is as follows:

  • The producer only produces when the warehouse is not full, and stops production when the warehouse is full.
  • Consumers can only consume when there are products in the warehouse, and wait when the warehouse is empty.
  • When the consumer finds that the warehouse has no products to consume, the producer will be notified to produce.
  • When a producer produces a consumable product, he should notify the waiting consumer to consume it.

Coding implementation

Warehouse category, the product is the attribute data of the warehouse

//临界资源
public class Basket {
    
    
	private volatile Object data;
	//生产者向仓库中存放数据
	public synchronized void product(Object data) {
    
    
		//如果仓库中有数据,则生产者进入阻塞等待,直到其它线程唤醒
		while(this.data!=null)
			try {
    
    
				this.wait();
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		//如果没有数据则进行生产操作
		this.data=data;
		System.out.println(Thread.currentThread().getName()+"生产了一个日期"+this.data);
		this.notifyAll();//唤醒在当前对象上处于wait的所有线程
	}
	//消费者从仓库中消费数据
	public synchronized void consume() {
    
    
		//如果仓库中没有数据,则消费者进入阻塞等待,直到其它线程唤醒
		while(this.data==null)
			try {
    
    
				this.wait();
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		//如果有数据data!=null,则执行消费操作
		System.out.println(Thread.currentThread().getName()+"消费了一个数据"+this.data);
		this.data=null;
		this.notifyAll();  //唤醒在当前对象上处于wait的所有线程
	}
}

The producer thread is responsible for producing products and sharing warehouses with consumers

public class Producer extends Thread {
    
    
	private Basket basket;
	//通过构造器传入对应的basket对象
	public Producer(Basket basket) {
    
    
		this.basket=basket;
	}
	@Override
	public void run() {
    
    
		//生产20次日期对象
		for(int i=0;i<20;i++) {
    
    
			Object data=new Date();  //生产者生产的具体产品
			basket.product(data);
		}
	}
}

The consumer thread is responsible for consuming the product and sharing the warehouse with the producer

public class Consumer extends Thread {
    
    
	private Basket basket;

	public Consumer(Basket basket) {
    
    
		this.basket = basket;
	}

	@Override
	public void run() {
    
    
		// 消费20次日期对象
		for (int i = 0; i < 20; i++) {
    
    
			basket.consume();
		}
	}
}

Producers and consumers share the same storage space in the same time period. Producers add products to the storage space, and consumers remove products from the storage space. When the storage space is empty, the consumer blocks, and when the storage space is full When the producer is blocked.

Why use the producer/consumer model

In the threaded world, the producer is the thread that produces data, and the consumer is the thread that consumes the data. In multi-threaded development, if the producer processing speed is very fast, but the consumer processing speed is very slow, then the producer must wait for the consumer to finish processing before continuing to produce data. In the same way, if the consumer's processing power is greater than that of the producer, then the consumer must wait for the producer. In order to solve this problem of the imbalance in production and consumption capacity, there is a producer and consumer model.

Advantages of the producer/consumer model

1. Decoupling. Because there is an extra buffer, the producer and the consumer do not directly call each other. This is easy to think of. In this way, the code of the producer and the consumer will not affect each other. In fact, the producer and the consumer will not be affected by each other. The strong coupling between the consumer and the consumer is unraveled, and it becomes the weak coupling between the producer and the buffer/consumer and the buffer

2. Improve the overall data processing speed by balancing the processing capabilities of producers and consumers. This is one of the most important advantages of the producer/consumer model. If the consumer gets the data directly from the producer, if the producer's production speed is very slow, but the consumer's consumption speed is very fast, then the consumer will have to take up the CPU time slice and wait there for nothing. With the producer/consumer model, the producer and the consumer are two independent concurrent entities. The producer just throws the produced data into the buffer, regardless of the consumer; the consumer is also from the buffer Just get the data, and you don’t have to worry about the producer. If the buffer is full, no production will be made, and if the buffer is empty, no consumption will be made, so that the processing power of the producer/consumer can reach a dynamic balance.

The role of the producer/consumer model

  • Support concurrency
  • Decoupling
  • Support uneven availability

Calling methods such as wait/notify must be inside the current thread object, such as in the synchronized method

Singleton

Ensure that there is only one instance in the VM

  • Hungry man mode
    • Private constructor
    • Private static Singleton instance=new Singleton();
    • Shared static method
  • Lazy man mode
    • Private constructor
    • Private static properties, do not create objects directly
    • Shared static method
public class Singleton {
    
    
	private Singleton() {
    
    }
	private static Singleton instance;
	public static Singleton getInstance() {
    
    
		if(instance==null)
			instance=new Singleton();   //当第一次使用对象时才进行创建
		return instance;
	}
}

There may be multiple creations of the object, how to solve it

public class Singleton {
    
    
	private Singleton() {
    
    }
	private static Singleton instance;
	public synchronized static Singleton getInstance() {
    
    
		if(instance==null)
			instance=new Singleton();   //当第一次使用对象时才进行创建
		return instance;
	}
}

Generally, it is not recommended to use a more granular lock processing mechanism, as concurrency will be affected

Double detection lazy mode

  • Create objects on demand to avoid useless creation operations

  • Thread safe

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

Use of Lock

Lock is a thread synchronization tool introduced in Java 1.5. It is mainly used to control shared resources under multiple threads. In essence, Lock is just an interface. Synchronization can be achieved by explicitly defining a synchronization lock object. It can provide a wider range of locking operations than synchronized and supports multiple related Condition objects.

  • void lock(); Attempt to acquire the lock, return if the acquisition is successful, otherwise block the current thread

void lockInterruptibly() throws InterruptedException; try to acquire the lock, the thread is interrupted before successfully acquiring the lock, then give up acquiring the lock and throw an exception

boolean tryLock(); Try to acquire the lock, return true if the lock is acquired successfully, otherwise return false

boolean tryLock(long time, TimeUnit unit) tries to acquire the lock. If the lock is acquired within the specified time, it returns true, otherwise it returns false. If it is interrupted before acquiring the lock, an exception is thrown.

  • void unlock(); release the lock
  • Condition newCondition(); returns the condition variable of the current lock. The function similar to notify and wait can be realized through the condition variable. A lock can have multiple condition variables

Lock has three implementation classes, one is ReentrantLock, and the other two are the two static internal classes ReadLock and WriteLock in the ReentrantReadWriteLock class.

Usage: When accessing (mutually exclusive) shared resources under multithreading, lock before access and unlock after access. It is recommended that the unlock operation be placed in the finally block.

private final ReentrantLock lock=new ReentrantLock();

In the specific method lock.lock() try{}finally{lock.unlock}

Lock lock=new ReentrantLock();//构建锁对象
try{
    
    
	lock.lock();//申请锁,如果可以获取锁则立即返回,如果锁已经被占用则阻塞等待
	System.out.println(lock);//执行处理逻辑
} finally{
    
    
	lock.unlock();//释放锁,其它线程可以获取锁
}

Example 1: Start 4 threads, perform 50 addition and subtraction operations on an int number, requiring 2 additions and 2 subtractions to ensure the thread safety of the output

public class OperNum {
    
    
	private int num=0;	
	private final static Lock lock=new ReentrantLock();  //构建锁对象
	public void add() {
    
    
		try {
    
    
			lock.lock();  //申请加锁操作,如果能加上则立即返回,否则阻塞当前线程
			num++;
			System.out.println(Thread.currentThread().getName()+"add..."+num);
		} finally {
    
    
			lock.unlock(); //具体实现采用的是重入锁,所以持有锁的线程可以多次申请同一个锁,但是申请加锁次数必须和释放锁的次数一致
		}
	}
	public void sub() {
    
    
		try {
    
    
			lock.lock();
			num--;
			System.out.println(Thread.currentThread().getName()+"sub..."+num);
		} finally {
    
    
			lock.unlock();
		}
	}
}

Condition interface

Condition is an interface provided under the juc package. It can be translated into a condition object. Its function is that the thread waits first, and when a certain condition is met externally, the waiting thread is awakened through the condition object.

void await() throws InterruptedException; Let the thread enter the waiting. If other threads call notify/notifyAll of the same Condition object, the waiting thread may be awakened. Release the lock

void signal(); wake up the waiting thread

void signalAll(); wake up all threads

Special points of using Condition:

  • When condition.await() is called to block the thread, the lock will be automatically released, no matter how many lock.lock() is called, then the thread blocked on the lock.lock() method can acquire the lock
  • When condition.signal() is called to wake up the thread, it will continue to execute at the last blocked position, and the lock will be automatically reacquired by default (note that the number of locks acquired during blocking is the same)

Guess you like

Origin blog.csdn.net/qq_43480434/article/details/114153429