如何通过Java并发锁实现这样的用例

如何通过Java并发锁实现这样的用例,java,multithreading,concurrency,atomic,volatile,Java,Multithreading,Concurrency,Atomic,Volatile,在下面的测试用例中,update()方法在类外调用,该类每小时只运行一个线程。但是有多个线程同时调用getKey1()和getKey2() 因此,对于这个用例: 最重要的是,由于getKey1()和getKey2()几乎同时调用,我们必须确保 如果标志为true,则更新键1和键2并返回新键 如果没有,则获取旧的键1和键2 我们不希望存在这样的情况:更新key1并获得旧的key2,但是对于很少的请求,即使我们已经更新了,也可以获得旧的key1和key2 public class Test {

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

因此,对于这个用例:

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

  • 如果标志为true,则更新键1和键2并返回新键
  • 如果没有,则获取旧的键1和键2
  • 我们不希望存在这样的情况:更新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;
        }
    }
    
    我的想法是:

  • 运行update()时,阻止
    getKey1()
    getKey2()
    ,等待获取更新密钥
  • 当update()未运行时,
    getKey1()
    getKey2()
    都应该正常并直接返回
  • 调用getKey1()或getKey2()时,我认为不需要阻止update()方法
  • 有人对实现有什么想法吗?

    使用tryLock()

    如果不希望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;
        }
    

    参考:

    正如前面所说的
    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
    界面的文档:

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


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

    在这种情况下,
    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)的东西可能会有所帮助,但这将涉及更多的编码,并且可能在这里被过度设计

    参考资料:


    在getKey1()和getKey2()中都使用lock.tryLock(),您确定
    ReentrantLock
    是您所需要的吗?您是否考虑过,您应该(至少)将
    isLocked
    声明为
    volatile
    ,因为无法保证读卡器线程将看到其值更改。在访问其他
    挥发物
    时存在内隐记忆障碍,但它们在
    isLocked
    更改之后或之前,因此没有帮助。也就是说,我不明白这能带来什么。如果在
    while(isLocked)之后获得锁在锁定期间,
    getKey1()
    的主体仍然可以执行。谢谢Persixty3。它应该是易变的。我仍然不知道代码实现了什么。锁定可以在该方法期间的任何时间获取。锁定可以实现。但是在执行更新操作时,getKey函数将等待锁被释放。但是它可以在方法中获取。该代码不能确保在读取期间未保持锁。它不能确保同步。
    getKey1()
    getKey2()
    通常同时被调用,但是如果更新发生在
    getKey1()
    getKey2()
    之间,它将读取旧的key1但最新的key2,该怎么办?有没有更好的解决方案来避免这种情况(getKey1()和getKey2())是从接口重写的,因此我们无法将两者合并到一个方法中。flag1和flag2仍然是原子布尔值吗?key1和key2仍然是易变的,因为它已经在锁中。实际上可以将flag1和flag2合并到一个字段中吗?在我找到大量internet搜索后。创建
    key1
    key的包装器2
    并使用以原子方式更新它们。getKey1()和getKey2()通常同时被调用,但是如果更新发生在getKey1()和getKey2()之间,它将读取旧的key1而不是最新的key2,该怎么办?有没有更好的解决方案来避免这种情况(getKey1()和getKey2())是从接口重写的,因此我们无法将这两种方法合并到一个方法中。我面临的问题是此类是从接口实现的。因此
    getKey1()
    getKey2()
    是重写方法,我无法将它们组合成一个方法。更新方法是find,我将在类外更新文件,然后执行writeLock@Jien在哪种情况下,根据提供的接口,无法确保
    update()
    不能在
    getKey1()
    getKey2()之间运行
    。因此,无论您做什么,您都将面临先读取旧密钥,然后读取新密钥的风险。您可能会找到某种方法来暂停对
    getK的链接调用之间的写入
    
    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;
     }