【问题标题】:Synchronization with threads与线程同步
【发布时间】:2013-03-18 09:11:36
【问题描述】:

我有一个两部分的问题...

  1. 我有一个类,其中有一个函数,在给定时间只能由任何一个线程访问。使它成为synchronized 函数或synchronized 块仍然允许多个线程,因为不同的线程在类中访问它。如何确保只有一个线程访问此代码? (见下面的代码示例)

  2. 使用同步函数,对该函数的调用会排队。有没有办法只允许最后一次调用函数来访问代码?因此,如果我当前有 Thread1 访问我的函数,那么 Thread2 和 Thread3 会尝试访问它(按此顺序),一旦 Thread1 完成,将只授予 Thread3 访问权限。

    public void doATask() {
        // I create a new thread so the interface is not blocked
        new Thread(new Runnable() {
    
            @Override
            public void run() {
                doBackgroundTask();
            }
        }).start();
    }
    
    private void doBackgroundTask(MyObject obj) {
        // perform long task here that is only being run by one thread
        // and also only accepts the last queued thread
    }
    

感谢您的帮助!

【问题讨论】:

  • 被取消的线程应该怎么办?他们应该被终止还是返回哨兵或抛出异常?是否应该在另一个线程尝试调用该方法时立即取消被取消的线程,还是应该在线程 1 完成时取消它?这里的歧义太多了,你想做什么?
  • 线程 2 发生了什么?它应该在线程 3 到达时退出吗?抛出一些异常?
  • 第二个线程应该被终止。它不需要抛出异常。它可以被忽略。并且可以随时取消被取消的线程......只要另一个线程尝试调用该方法或当 Thread1 完成时。
  • @Nick:如果是这样,那么您可能不想使用线程,而是可能需要某种事件驱动系统。
  • 好吧,doATask() 函数是由用户按下按钮引起的。在这种情况下,用户正在扫描一堆对象,以便他们快速点击按钮。

标签: java synchronization synchronized synchronized-block


【解决方案1】:

如果您的示例中的第二个线程只能return,您可以使用锁和跟踪执行该方法的最后一个线程的组合。它可能看起来像这样:

private volatile Thread lastThread;
private final ReentrantLock lock = new ReentrantLock();

private void doBackgroundTask(Object obj) throws InterruptedException {
    Thread currentThread = Thread.currentThread();
    lastThread = currentThread;
    try {
        // wait until lock available
        lock.lockInterruptibly();
        // if a thread has arrived in the meantime, exit and release the lock
        if (lastThread != currentThread) return; 
        // otherwise
        // perform long task here that is only being run by one thread
        // and also only accepts the last queued thread
    } finally {
        lock.unlock();
    }
}

带有额外日志记录的完整工作测试显示线程交错并且 T2 不执行任何操作就退出:

class Test {

    private volatile Thread lastThread;
    private final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        final Test instance  = new Test();
        Runnable r = new Runnable() {

            @Override
            public void run() {
                try {
                    instance.doBackgroundTask(null);
                } catch (InterruptedException ignore) {}
            }
        };
        Thread t1 = new Thread(r, "T1");
        Thread t2 = new Thread(r, "T2");
        Thread t3 = new Thread(r, "T3");
        t1.start();
        Thread.sleep(100);
        t2.start();
        Thread.sleep(100);
        t3.start();
    }

    private void doBackgroundTask(Object obj) throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        System.out.println("[" + currentThread.getName() + "] entering");
        lastThread = currentThread;
        try {
            // wait until lock available
            lock.lockInterruptibly();
            // if a thread has arrived in the meantime, exit and release the lock
            if (lastThread != currentThread) return;
            // otherwise
            // perform long task here that is only being run by one thread
            // and also only accepts the last queued thread
            System.out.println("[" + currentThread.getName() + "] Thinking deeply");
            Thread.sleep(1000);
            System.out.println("[" + currentThread.getName() + "] I'm done");
        } finally {
            lock.unlock();
            System.out.println("[" + currentThread.getName() + "] exiting");
        }
    }
}

输出:

[T1] entering
[T1] Thinking deeply
[T2] entering
[T3] entering
[T1] I'm done
[T1] exiting
[T2] exiting
[T3] Thinking deeply
[T3] I'm done
[T3] exiting

【讨论】:

    【解决方案2】:

    你想要的可能是一个等待信号做一些工作的工作线程。 doATask() 只是发送一个信号来触发工作。累积信号相当于一个信号。

    final Object lock = new Object();
    MyObject param = null;
    
    public void doATask(arg) 
        synchronized(lock)
            param=arg;
            lock.notify();
    
    MyObject awaitTask()
        synchronized(lock)
            while(param==null)
                lock.wait();
            tmp=param;
            param=null;
            return tmp;
    
    // worker thread
    
    public void run()
        while(true)
            arg = awaitTask();
            doBackgroundTask(arg);
    

    【讨论】:

    • 这确实是另一种看待它的方式 - 在这种情况下,您可以简单地使用阻塞队列或一些类似的机制而不是等待/通知。
    • 是的,但队列只需要保留最后一项。不确定哪个队列实现了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-20
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 2012-02-19
    相关资源
    最近更新 更多