Concurrent programming: how to use locks in java?

The depth of Java lock

When multiple requests operate the database at the same time, first change the order status to paid, and add 200 to the amount, which will cause duplicate notifications under the concurrent scenario query condition.
SQL:
Update

Pessimistic lock and optimistic lock

Pessimistic lock pessimistic that every operation will cause the problem of update loss, in each query, add exclusive lock.

Every time I go to get the data, I think other people will modify it, so every time I get the data, I lock it, so that when other people want to get the data, they block it until it gets the lock. In traditional relational database, many lock mechanisms are used, such as row lock, table lock, read lock, write lock, etc., which are locked before operation.

Select * from xxx for update;

Optimistic lock: optimistic lock will be optimistic that each query will not result in update loss, using version field control

Common optimistic lock framework

Reentry lock

As a tool to share data and ensure consistency, locks are implemented in JAVA platform (such as synchronized, ReentrantLock, etc.). These locks have been written to provide convenience for our development.

Reentry lock, also called recursive lock, refers to that after the outer function of the same thread obtains the lock, the inner recursive function still has the code to acquire the lock, but is not affected.

ReentrantLock and synchronized are reentrant locks in JAVA environment

public class Test implements Runnable {
	public  synchronized void get() {
		System.out.println("name:" + Thread.currentThread().getName() + " get();");
		set();
	}

	public synchronized  void set() {
		System.out.println("name:" + Thread.currentThread().getName() + " set();");
	}

	@Override

	public void run() {
		get();
	}

	public static void main(String[] args) {
		Test ss = new Test();
		new Thread(ss).start();
		new Thread(ss).start();
		new Thread(ss).start();
		new Thread(ss).start();
	}
}

public class Test02 extends Thread {
	ReentrantLock lock = new ReentrantLock();
	public void get() {
		lock.lock();
		System.out.println(Thread.currentThread().getId());
		set();
		lock.unlock();
	}
	public void set() {
		lock.lock();
		System.out.println(Thread.currentThread().getId());
		lock.unlock();
	}
	@Override
	public void run() {
		get();
	}
	public static void main(String[] args) {
		Test ss = new Test();
		new Thread(ss).start();
		new Thread(ss).start();
		new Thread(ss).start();
	}

}

CAS NO lock mechanism

(1) compared with locks, using comparison exchange (hereinafter referred to as CAS) will make the program look more complex. But because of its non blocking, it is immune to deadlock, and the interaction between threads is far smaller than the lock based way. More importantly, there is no system overhead caused by lock competition and frequent scheduling between threads in the unlocked mode. Therefore, it has better performance than the lock based mode.

(2) advantages of no lock:
First, in the case of high concurrency, it has better performance than the program with lock;
Second, it is inherently immune to deadlock.

With these two advantages, it is worth taking a risk to try to use unlocked concurrency.

(3) the process of CAS algorithm is as follows: it contains three parameters CAS(V,E,N): V represents the variable to be updated, e represents the expected value, and N represents the new value. Only when the V value is equal to the E value, will the V value be set to N. if the V value is different from the E value, it means that other threads have updated, and the current thread does nothing. Finally, CAS returns the true value of the current v.

(4) CAS operation is carried out with an optimistic attitude. It always thinks that it can complete the operation successfully. When multiple threads use CAS to operate a variable at the same time, only one will win and update successfully, and the rest will fail. The failed thread will not be suspended. It is only informed of the failure, and it is allowed to try again. Of course, the failed thread is also allowed to give up the operation. Based on this principle, CAS operation can find the interference of other threads to the current thread even if there is no lock, and handle it properly.

(5) simply put, CAS requires you to give an additional expectation, that is, what do you think this variable should look like now. If the variable is not what you think, it means that it has been modified by others. You can read it again and try to modify it again.

(6) at the hardware level, most modern processors have supported the atomized CAS instruction. After JDK 5.0, virtual machine can use this instruction to implement concurrent operation and concurrent data structure, and this operation is ubiquitous in virtual machine.

/** 
	 * Atomically increments by one the current value. 
	 * 
	 * @return the updated value 
	 */  
	public final int incrementAndGet() {  
	    for (;;) {  
	        //Get current value  
	        int current = get();  
	        //Set expectations  
	        int next = current + 1;  
	        //Call the Native method compareAndSet to perform CAS operation  
	        if (compareAndSet(current, next))  
	            //The expected value will be returned only after success, otherwise the wireless loop  
	            return next;  
	    }  
	}  

Spin lock

Spin lock is implemented by making the current thread continuously execute in the loop body. When the conditions of the loop are changed by other threads, it can enter the critical area. as follows

	private AtomicReference<Thread> sign =new AtomicReference<>();
	public void lock() {
		Thread current = Thread.currentThread();
		while (!sign.compareAndSet(null, current)) {
          }
	}
	public void unlock() {
		Thread current = Thread.currentThread();
		sign.compareAndSet(current, null);
	}

public class Test implements Runnable {
	static int sum;
	private SpinLock lock;

	public Test(SpinLock lock) {
		this.lock = lock;
	}

	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		SpinLock lock = new SpinLock();
		for (int i = 0; i < 100; i++) {
			Test test = new Test(lock);
			Thread t = new Thread(test);
			t.start();
		}

		Thread.currentThread().sleep(1000);
		System.out.println(sum);
	}

	@Override
	public void run() {
		this.lock.lock();
		this.lock.lock();
		sum++;
		this.lock.unlock();
		this.lock.unlock();
	}

}

When a thread calls the non reentrant spin lock to add a lock, it is OK. When it calls lock() again, because the hold reference of the spin lock is no longer empty, the thread object will mistakenly think that someone else's thread holds the spin lock

With CAS atomic operation, the lock function sets the owner to the current thread and predicts that the original value is null. The unlock function sets the owner to null and the prediction value is the current thread.

When the second thread calls the lock operation, because the owner value is not empty, the loop is executed all the time. Until the first thread calls the unlock function to set the owner to null, the second thread can enter the critical area.

Because the spin lock just keeps the current thread executing the loop body and does not change the thread state, the response speed is faster. However, when the number of threads keeps increasing, the performance drops obviously, because each thread needs to execute and takes up CPU time. If the thread competition is not fierce and the lock is held for a period of time. Suitable for spin lock.

Distributed lock

If you want to ensure data synchronization in different JVMs, use distributed locking technology.

There are database implementation, cache implementation

Tags: Java Database SQL REST

Posted on Mon, 22 Jun 2020 01:44:27 -0400 by CantonWeb