简体   繁体   中英

How to implement such use case by Java Concurrency Lock

In the test case below, the update() method is called outside of the class which in running for every hour by one thread only. But there have multiple threads calling getKey1() and getKey2() at the same time.

So for this use case:

The most important is since getKey1() and getKey2() are calling almost at the same time, we must make sure

  1. if flag is true, update both key1 and key2 and return the new key
  2. if not, get the old key1 and key2

We donot want such case exist: update the key1 and get the old key2, but it's ok to get the old key1 and key2 for few request even we already update it.

public class Test {

    private Lock lock = new ReentrantLock();

    private AtomicBoolean flag1 = new AtomicBoolean(false);
    private AtomicBoolean flag2 = new AtomicBoolean(false);

    private volatile String key1;
    private volatile String key2;

    public Test(String key1, String key2) {
        this.key1 = key1;
        this.key2 = key2;
    }

    public void update() {
        lock.lock();
        try {
            flag1.compareAndSet(false, true);
            flag2.compareAndSet(false, true);
        } finally {
            lock.unlock();
        }
    }

    public String getKey1() {
        // TODO if the lock is holding... block over here
        if (flag1.get()) {
            // doing something for key 1
            key1 = getFromFile();
            flag1.set(false);
        }
        return key1;
    }

    public String getKey2() {
        // TODO if the lock is holding... block over here
        if (flag2.get()) {
            // doing something for key 1
            key2 = getFromFile();
            flag2.set(false);
        }
        return key1;
    }
}

My idea is:

  1. when update() is running, block both getKey1() and getKey2() , waiting to get the update key
  2. when update() is not running, getKey1() and getKey2() should both be ok and return directly.
  3. when calling either getKey1() or getKey2(), I think we do not need to block the update() method.

Anyone have any ideas about the implementation?

As already said java.util.concurrent.locks.ReentrantReadWriteLock suits you best.

To put it in your example :

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test {

    private ReadWriteLock lock = new ReentrantReadWriteLock();

    private AtomicBoolean flag1 = new AtomicBoolean(false);
    private AtomicBoolean flag2 = new AtomicBoolean(false);

    private volatile String key1;
    private volatile String key2;

    public Test(String key1, String key2) {
        this.key1 = key1;
        this.key2 = key2;
    }

    public void update() {
        Lock writeLock = lock.writeLock();
        try {
            flag1.compareAndSet(false, true);
            flag2.compareAndSet(false, true);
        } finally {
            writeLock.unlock();
        }
    }

    public String getKey1() {
        Lock readLock = lock.readLock();
        try {
            if (flag1.get()) {
                // doing something for key 1
                key1 = getFromFile();
                flag1.set(false);
            }
            return key1;
        } finally {
            readLock.unlock();
        }
    }

    public String getKey2() {
        Lock readLock = lock.readLock();
        try {
            if (flag2.get()) {
                // doing something for key 1
                key2 = getFromFile();
                flag2.set(false);
            }
            return key1;
        } finally {
            readLock.unlock();
        }
    }
}

From docs of ReadWriteLock interface:

The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.

An update on the below ReadWriteLock sample, we have to acquire locks before starting the read/write operations. As I don't have permission to edit the sample, posting a new answer.

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test {

    private ReadWriteLock lock = new ReentrantReadWriteLock();

    private AtomicBoolean flag1 = new AtomicBoolean(false);
    private AtomicBoolean flag2 = new AtomicBoolean(false);

    private volatile String key1;
    private volatile String key2;

    public Test(String key1, String key2) {
        this.key1 = key1;
        this.key2 = key2;
    }

    public void update() {
        Lock writeLock = lock.writeLock();
        try {
            writeLock.lock(); 
            flag1.compareAndSet(false, true);
            flag2.compareAndSet(false, true);
        } finally {
            writeLock.unlock();
        }
    }

    public String getKey1() {
        Lock readLock = lock.readLock();
        try {
            readLock.lock(); 
            if (flag1.get()) {
                // doing something for key 1
                key1 = getFromFile();
                flag1.set(false);
            }
            return key1;
        } finally {
            readLock.unlock();
        }
    }

    public String getKey2() {
        Lock readLock = lock.readLock();
        try {
            readLock.lock();
            if (flag2.get()) {
                // doing something for key 1
                key2 = getFromFile();
                flag2.set(false);
            }
            return key1;
        } finally {
            readLock.unlock();
        }
    }
}

This is certainly a case where a ReadWriteLock should help. Many concurrent readers can operate with miniscule contention and very infrequently a writer acquires the write-lock and updates.

However the key issue raised is that a thread must not read one old key and one new key in a single pass.

That's a classical problem. The easiest solution is to update both keys in the update thread.

ReentrantReadWriteLock rwlock=new ReentrantReadWriteLock(true);
//If in doubt require a fair lock to avoid live-locking writes...

//...

public void update() {
    String new1=getFromFile();
    String new2=getFromFile();
    //That's the heavy lifting. Only now acquire the lock...
    Lock wlock=rwlock.writeLock();
    wlock.lock();
    try {
        key1=new1;
        key2=new2;
    } finally {
        wlock.unlock();
    }
}

And in the readers:

public String[] getKeys() {
    String k1;
    String k2;
    Lock rlock=rwlock.readLock();
    rlock.lock();
    try {
        k1=key1;
        k2=key2;
    } finally {
        rlock.unlock();
    }    
    String[] r={k1,k2};
    return r;
 }

A read-write lock has two connected locks. Multiple readers can acquire the read-lock but the write lock is exclusive of readers and other writers. It is a common scenario and has established solutions.

It may be important to get from the file before acquiring the lock as I/O is often relatively slow and you should hold the lock for the minimum possible time.

It's also important to return both keys from the same acquisition of the lock. There's no other easy way to stop update happening between calls to getKey1() and getKey2() .

You no longer need to use the volatile keyword because of the memory barrier guarantees of the lock (see doc.).

You don't actually need a re-entrant lock but that is what is provided out-of-the-box for a read-write lock.

This is a case where something called a sequential lock (Seqlock) could help but that will involve more coding and may be over-engineered here.

References:

https://en.wikipedia.org/wiki/Seqlock

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html

Use tryLock()

public String getKey1() {
  if (lock.tryLock())
  {
    // Got the lock
    try
    {
        // Process record
    }
    finally
    {
        // Make sure to unlock so that we don't cause a deadlock
        lock.unlock();
    }
 }
}

If you don't want getKey1() or getKey2() to perform locking. You can use the following approach,

static boolean isLocked=false;
public void update() {
        lock.lock();
        isLocked = true;
        try {
            flag1.compareAndSet(false, true);
            flag2.compareAndSet(false, true);
        } finally {
            lock.unlock();
            isLocked = false; 
        }
    }

    public String getKey1() {
        while(isLocked); #wait till the lock release.
        if (flag1.get()) {
            // doing something for key 1
            key1 = getFromFile();
            flag1.set(false);
        }
        return key1;
    }

Ref : how-do-determine-if-an-object-is-locked-synchronized-so-not-to-block-in-java

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM