如何通过Java Concurrency Lock实现这样的用例

时间:2017-06-07 05:41:42

标签: java multithreading concurrency atomic volatile

在下面的测试用例中,update()方法在类外部调用,该方法仅在每小时运行一个线程。但是有多个线程同时调用getKey1()getKey2()

对于这个用例:

最重要的是,由于getKey1()getKey2()几乎同时呼叫,我们必须确保

  1. 如果flag为true,则更新key1和key2并返回新密钥
  2. 如果没有,请获取旧的key1和key2
  3. 我们不希望存在这样的情况:更新key1并获取旧的key2,但即使我们已经更新它,也可以获得旧的key1和key2,即使我们已经更新了它。

    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;
        }
    }
    

    我的想法是:

    1. 当update()运行时,阻止getKey1()getKey2(),等待获取更新密钥
    2. 当update()未运行时,getKey1()getKey2()都应该可以直接返回。
    3. 在调用getKey1()或getKey2()时,我认为我们不需要阻止update()方法。
    4. 有人对实施有任何想法吗?

4 个答案:

答案 0 :(得分:0)

正如已经说过java.util.concurrent.locks.ReentrantReadWriteLock最适合你。

将它放在你的例子中:

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();
        }
    }
}

来自ReadWriteLock界面的文档:

  

读锁可以由多个读取器线程同时保持,   只要没有作家。写锁是独占的。

答案 1 :(得分:0)

以下ReadWriteLock示例的更新,我们必须在开始读/写操作之前获取锁。由于我无权编辑样本,因此发布新答案。

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();
        }
    }
}

答案 2 :(得分:0)

这肯定是ReadWriteLock应该有帮助的情况。 许多并发读者可以在极小的争用下进行操作,并且编写器很少获得写锁定和更新。

然而,提出的关键问题是线程不能一次读取一个旧密钥和一个新密钥。

这是一个经典问题。最简单的解决方案是更新更新线程中的两个密钥。

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();
    }
}

在读者中:

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;
 }

读写锁具有两个连接的锁。多个读取器可以获取读锁定,但写锁定不包括读取器和其他写入器。这是一种常见的情况,已经建立了解决方案。

从强获取锁之前的文件获取锁定可能很重要,因为I / O通常相对较慢,您应该在最短的时间内保持锁定。

从同一个锁的获取中返回两个键也很重要。在getKey1()getKey2()的来电之间没有其他简单的方法可以阻止更新。

由于锁的内存屏障保证,您不再需要使用volatile关键字(请参阅文档)。

您实际上并不需要可重入锁定,但这是开箱即用的读写锁定。

在这种情况下,称为顺序锁(Seqlock)的东西可能有所帮助,但这将涉及更多编码,并且可能在这里过度设计。

参考文献:

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

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

答案 3 :(得分:-2)

使用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();
    }
 }
}

如果您不希望getKey1()或getKey2()执行锁定。您可以使用以下方法,

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;
    }

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