【问题标题】:How to synchronize threads of different classes on the same method如何在同一个方法上同步不同类的线程
【发布时间】:2017-05-10 22:45:54
【问题描述】:

我的疑问涉及同步使用相同方法的不同类的线程的方式。我有两个不同的类,ClientAClientB(显然扩展了 Thread)和一个服务器类的方法。 ClassAClassB的线程都必须使用这个方法,但是访问的策略不同:

  • 如果ClassA的线程正在使用方法内部的资源,同一个类的其他线程可以使用它;
  • 如果ClassB 的线程正在使用该方法,则没有人(ClassAClassB 的线程)可以使用它(互斥)。

所以这是我的问题:如何应用此同步策略?我在方法的开头使用了一个信号量(互斥体),但我不确定这个解决方案,因为我认为这每次都会阻塞所有类型的线程。

【问题讨论】:

  • ReadWriteLock 可以完成这项工作,但您必须花时间确定开销是否值得。
  • 感谢您的回答。你认为没有不同的方式来使用信号量吗?

标签: java multithreading mutex semaphore


【解决方案1】:

我认为你可以使用 java.util.concurrent.locks.ReentrantLock 来实现。

示例略有不同。 ClientA 让所有进入,ClientB 没有:

private final ReentrantLock lock = new ReentrantLock();
    private void method(boolean exclusive){
        log.debug("Before lock() ");
            try {
                lock.lock();
                if (!exclusive) {
                    lock.unlock();
                }

                log.debug("Locked part");
                //Method tasks....
                Thread.sleep(1000);
            }catch(Exception e){
                log.error("",e);
            }finally {
                if(exclusive)
                    lock.unlock();
            }
         log.debug("End ");
    }

执行:

new Thread("no-exclusive1"){
    @Override
    public void run() {
        method(false);
    }
}.start();
new Thread("exclusive1"){
    @Override
    public void run() {
        method(true);
    }
}.start();
new Thread("exclusive2"){
    @Override
    public void run() {
        method(true);
    }
}.start();
new Thread("no-exclusive2"){
    @Override
    public void run() {
        method(false);
    }
}.start();

结果:

01:34:32,566 DEBUG (no-exclusive1) Before lock() 
01:34:32,566 DEBUG (no-exclusive1) Locked part
01:34:32,566 DEBUG (exclusive1) Before lock() 
01:34:32,566 DEBUG (exclusive1) Locked part
01:34:32,567 DEBUG (exclusive2) Before lock() 
01:34:32,567 DEBUG (no-exclusive2) Before lock() 
01:34:33,566 DEBUG (no-exclusive1) End 
01:34:33,567 DEBUG (exclusive1) End 
01:34:33,567 DEBUG (exclusive2) Locked part
01:34:34,567 DEBUG (exclusive2) End 
01:34:34,567 DEBUG (no-exclusive2) Locked part
01:34:35,567 DEBUG (no-exclusive2) End 

【讨论】:

  • 所以我应该使用一种布尔“标志”来决定继续进行的方式。我已经考虑过这个解决方案。谢谢你的回答!
【解决方案2】:

可能有点复杂,但我认为展示您如何考虑仅使用同步块进行此操作会很有趣:

public class Server() {

    private LinkedList<Object> lockQueue = new LinkedList<Object>();

    public void methodWithMultipleAccessPolicies(Object client) {
        Object clientLock = null;

        // These aren't really lock objects so much as they are
        // the targets for synchronize blocks.  Get a different
        // type depending on the type of the client.
        if (client instanceof ClientA) {
            clientLock = new ALock();
        } else {
            clientLock = new BLock();
        }

        // Synchronize on the "lock" created for the current client.
        // This will never block the current client, only those that
        // get to the next synchronized block afterwards.
        synchronized(clientLock) {
            List<Object> locks = null;

            // Add it to the end of the queue of "lock" objects.
            pushLock(clientLock);

            // Instances of ClientA wait for all instances of
            // ClientB already in the queue to finish.  Instances
            // of ClientB wait for all clients ahead of them to
            // finish.
            if (client instanceof ClientA) {
                locks = getBLocks();
            } else {
                locks = getAllLocks();
            }
            // Where the waiting occurs.
            for (Lock lock : locks) {
                synchronized(lock) {}
            }

            // Do the work.  Instances of ClientB should have
            // exclusive access at this point as any other clients
            // added to the lockQueue afterwards will be blocked
            // at the for loop.  Instances of ClientA should
            // have shared access as they would only be blocked
            // by instances of ClientB ahead of them.
            methodThatDoesTheWork();

            // Remove the "lock" from the queue.
            popLock(clientLock);
        }
    }

    public void methodThatDoesTheWork() {
        // Do something.
    }

    private synchronized void pushLock(Object lock) {
        lockQueue.addLast(lock);
    }

    private synchronized void popLock(Object lock) {
        lockQueue.remove(lock);
    }

    private synchronized List<Object> getAllLocks() {
        return new ArrayList<Object>(lockQueue);
    }

    private synchronized List<Object> getBLocks() {
        ArrayList<Object> bLocks = new ArrayList<>();
        for (Object lock : lockQueue) {
            if (lock instanceof BLock) {
                bLocks.add(lock);
            }
        }
    }
}

public class ALock {}
public class BLock {}

【讨论】:

  • 这个解决方案看起来比另一个更复杂,但肯定有用。我可以使用与ReentrantLock 关联的QueuedThreads 列表而不是LinkedLIst&lt;Object&gt; 吗?谢谢你的回答!
  • 您真的只需要列表中的简单 java 对象。锁定完全来自同步块。如果您按照预期的方式使用 ReentrantLocks,它可能会与同步块冲突。
  • 哦,如果你使用它,还要确保你彻底测试它。很确定这是一个可靠的解决方案,但它也完全出自我的脑海。 :)
  • 为了确保您知道,同步进行了三个级别。 1)外部同步块。 2)内部for循环。 3) 修改队列的同步方法调用。这三个都很重要。此外,一切发生的顺序也很重要。我吓到你了吗? :)
  • 非常感谢!您的 cmets 非常非常有用!
猜你喜欢
  • 2015-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-09
  • 1970-01-01
  • 1970-01-01
  • 2017-05-03
相关资源
最近更新 更多