Java 与线程同步

Java 与线程同步,java,synchronization,synchronized,synchronized-block,Java,Synchronization,Synchronized,Synchronized Block,我有一个两部分的问题 我有一个类,其中有一个函数,在给定的时间内只能由任何一个线程访问。将此设置为同步函数或同步块仍然允许多个线程,因为类中有不同的线程访问它。如何确保只有一个线程访问此代码?(参见下面的代码示例) 使用synchronized函数,对该函数的调用将排队。有没有办法只允许对函数的最后一次调用访问代码?因此,如果我让Thread1当前正在访问我的函数,那么Thread2和Thread3尝试访问它(按顺序),Thread1完成后,只有Thread3可以访问 public void d

我有一个两部分的问题

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

  • 使用synchronized函数,对该函数的调用将排队。有没有办法只允许对函数的最后一次调用访问代码?因此,如果我让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
    }
    

  • 谢谢你的帮助

    如果示例中的第二个线程只能
    返回
    ,则可以使用锁和跟踪执行该方法的最后一个线程的组合。它可能是这样的:

    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]进入
    [T1]深思熟虑
    [T2]进入
    [T3]进入
    [T1]我做完了
    [T1]退出
    [T2]退出
    [T3]深思熟虑
    [T3]我做完了
    [T3]退出
    
    如果示例中的第二个线程只能
    返回
    ,则可以使用锁和跟踪执行该方法的最后一个线程的组合。它可能是这样的:

    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]进入
    [T1]深思熟虑
    [T2]进入
    [T3]进入
    [T1]我做完了
    [T1]退出
    [T2]退出
    [T3]深思熟虑
    [T3]我做完了
    [T3]退出
    
    您需要的可能是一个工作线程,它等待信号来执行某些工作
    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);
    

    您需要的可能是一个工作线程,它等待信号来执行某些工作
    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);
    

    被取消的线程应该做什么?他们应该被终止还是返回哨兵或抛出异常?被取消的线程应该在另一个线程尝试调用该方法时立即取消,还是在线程1完成时取消?这里有太多的歧义,你想做什么?线程2会发生什么?它应该在线程3到达时退出吗?抛出一些异常?第二个线程应该被终止。它不需要抛出异常。可以忽略它。被取消的线程可以在任何时候被取消……只要另一个线程尝试调用该方法,或者当Thread1完成时。@Nick:如果是这种情况,那么您可能不想使用线程,而可能需要某种事件驱动的系统。嗯,函数doATask()是由用户按下按钮引起的。在这种情况下,用户正在扫描一堆对象,因此他们正在快速点击按钮。被取消的线程应该做什么?他们应该被终止还是返回哨兵或抛出异常?被取消的线程应该在另一个线程尝试调用该方法时立即取消,还是在线程1完成时取消?这里有太多的歧义,你想做什么?线程2会发生什么?它应该在线程3到达时退出吗?抛出一些异常?第二个线程应该被终止。它不需要抛出异常。可以忽略它。被取消的线程可以在任何时候被取消……只要另一个线程尝试调用该方法,或者当Thread1完成时。@Nick:如果是这种情况,那么您可能不想使用线程,而可能需要某种事件驱动的系统。嗯,函数doATask()是由用户按下按钮引起的。在这种情况下,用户正在扫描一堆对象,因此他们正在快速点击按钮。这确实是另一种看待它的方式-在这种情况下,您可以简单地使用阻塞队列或类似的机制,而不是等待/通知。是的,但队列只需要保留最后一项。不确定哪个队列实现了这一点。这确实是另一种看待它的方式——在这种情况下,您可以简单地使用阻塞队列或类似的机制,而不是等待/通知。是的,但是队列只需要保留最后一项。不确定哪个队列实现了这个。