锁定方法直到完成另一个方法

时间:2016-10-14 19:52:29

标签: java multithreading locking

我有一个我必须调用的外部API需要验证令牌。调用API的应用程序将是线程化的。此外,只允许5个并发连接。我将使用固定的线程池进行连接,但是我遇到了解决如何处理过期/无效令牌的问题。我想要做的是,当一个线程遇到过期的令牌时,阻止其他线程获取令牌,直到它被刷新为止。我正在考虑使用ReentrantLock来做这件事,但我不确定我的实现是否正确。

public static void main(String[] args){

    for(int i = 0; i < 100; i++){
        new Thread(new LockTest()).start();
    }
}

public void testLock(String message) throws InterruptedException{ 
    try{
        getToken(message);

        /*
         * Use token here
         */
        Thread.sleep(1000);

        Random r = new Random();
        int num = r.nextInt((25-0) + 1);

        if(num == 1){ //testing only - exception thrown randomly.
            throw new Exception("Token Expired!");
        }

        System.out.println("Message: " +  message);

    }catch(Exception e){
        System.err.println(e.getMessage());
        awaitTokenRefresh = true;
        refreshToken(); 
    }
}

private void refreshToken() throws InterruptedException {
    lock.lock();
    try{
        System.out.println("Refreshing token...");
        Thread.sleep(2000l);
        System.out.println("Refreshed!");
        awaitTokenRefresh = false;
        awaitRefresh.signalAll();
    }
    finally{
        lock.unlock();
    }   
}

//test use case for salesforce token
private void getToken(String message) throws InterruptedException {
    lock.lock();
    try{
        while(awaitTokenRefresh){
            System.out.println(message + " waiting for token refresh...");
            awaitRefresh.await();
        }
    }
    finally{
        lock.unlock();
    }
}

public void run(){
    try {
        System.out.println("Starting thread...");
        testLock(Thread.currentThread().getName());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

只是为了测试,我已经有些睡觉来模仿正在进行的工作。我不知道的主要事情是,当线程A在getToken()内部解锁时,线程B进入,但我们不知道令牌是否无效。所以B实际上可能会得到A必须找到的坏令牌。有没有一个好方法来处理这个?或者完全错误地使用锁的想法是什么?

2 个答案:

答案 0 :(得分:2)

我注意到的第一件事是你的代码没有正确同步。 testLock()中的异常处理程序修改共享变量awaitTokenRefresh,其中该写入未相对于在getToken()中读取其值的其他线程进行排序。

  

我不知道的主要事情是,当线程A在getToken()内部解锁时,线程B进入,但我们还不知道令牌是否无效。所以B实际上可能会得到A必须找到的坏令牌。有没有一个好方法来处理这个?或者完全错误地使用锁的想法是什么?

我猜你真正想要避免的是当前令牌变得无效时不必要的令牌刷新。正好一个线程应该刷新它;其他人应该等待刷新,然后继续他们的业务。您的方法的问题是线程没有好的方法来确定它们是否是第一个检测到期的,因此应该负责刷新。确实这是有道理的,因为在多线程应用程序中,哪个线程首先任何的概念并不总是很好定义。

是否使用锁定与同步是一个相对较小结果的实现细节。关键是你必须有一些共享状态告诉线程他们建议刷新的令牌实际上是否仍然是当前的。我可能会这样实现:

public class MyClass {
    private Object token = null;
    private final Object tokenMonitor = new Object();

    // ...

    private Object getToken() {
        synchronized (tokenMonitor) {
            if (token == null) {
                return refreshToken(null);
            } else {
                return token;
            }
        }
    }

    private Object refreshToken(Object oldToken) {
        synchronized (tokenMonitor) {
            if (token == oldToken) {  // test reference equality
                token = methodToPerformARefreshAndGenerateANewToken();
            }
            return token;
        }
    }

    // ...
}

当它尝试刷新令牌时,每个线程指定它正试图刷新哪个令牌。只有在实际上是当前令牌时才执行刷新,无论哪种方式,都会返回当前令牌。

您可以使用ReentrantLock代替我的tokenMonitor,使用锁定和解锁而不是同步块,但是当范围包含得很好时,我更喜欢简单同步,就像在这种情况下一样。除此之外,它更安全 - 当你离开同步块时,你就离开它;没有可能无法释放相关的监视器。锁对象也不能这样说。

答案 1 :(得分:0)

这实际上看起来是一个可以通过版本控制来解决的问题:

public class LockTest {

    private int currentVersion = -1;
    private Object token = null;

    private synchronized int refreshToken(int requestorVersion) {
        if (requestorVersion == currentVersion) {
            try {
                //do the actual refresh
                Thread.sleep(1000);
                token = new Object();
                currentVersion++;
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }

        return currentVersion;
    }

    public VersionedToken takeToken() {
        if (currentVersion == -1) {
            refreshToken(-1);
        }

        return new VersionedToken(currentVersion);
    }

    public class VersionedToken {

        private int version;

        public VersionedToken(int version) {
            this.version = version;
        }

        private void refresh() {
            version = refreshToken(version);
        }

        private Object getToken() {
            return token;
        }

    }

    public static void main(String[] args) {
        LockTest t = new LockTest();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                VersionedToken vtoken = t.takeToken();
                Object token = vtoken.getToken();

                try {
                //do something with the token 
                }catch (Exception ex) {
                    //if token went bad - just refresh it and continue to work with it afterwords
                    vtoken.refresh();
                    token = vtoken.getToken();
                }
            }).start();
        }
    }

}