【问题标题】:Syncronisize two looping Java-Thread同步两个循环的 Java-Thread
【发布时间】:2015-07-14 12:09:11
【问题描述】:

两个线程并行运行:

线程1:

while(...) {
  <-- wait until thread2 is not in update()
  doWork();
}

线程2:

while(...) {
  doWork();
  <-- wait until thread1 is not in work()
  update();
}

我认为上面的示例解释了我尝试做的事情,但我真的不知道如何进行这种同步。 thread2update() 方法很关键,在执行时 thread2 必须等待。

编辑:

到目前为止,感谢您的回答。不止一个运行良好。有人问我我想做什么,我想为此做一个简短的更新。

基于currentState 线程2 计算nextState 并在重复计算之前交换两者。 thread1 在 gui 中为用户显示“currentState”。

当前正在进行交换时,Thread1 不应显示currentState

就是这样。

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    使用Lock 是最简单的方法。这是一个简单的演示。我们创建两个Runnable 并让它们在两个线程下运行。然后我们等待 30 秒,然后打断它们并等待它们完成。

    // Lock shared between both threads.
    final Lock lock = new ReentrantLock();
    // Random numbers.
    final Random random = new Random();
    
    public void test() throws InterruptedException {
        // Process 1 is a runnable.
        Runnable p1 = new Runnable() {
    
            @Override
            public void run() {
                while (true) {
                    try {
                        // Grab the lock.
                        lock.lock();
                        // Do my work.
                        doWork();
                    } catch (InterruptedException ex) {
                        System.out.println("P1 Interrupted!");
                        break;
                    } finally {
                        // Release the lock in a `finally` to ensure it can never be left locked.
                        lock.unlock();
                    }
                }
            }
    
            private void doWork() throws InterruptedException {
                long wait = random.nextInt(2000);
                System.out.println("P1 Working ... " + wait);
                // Wait up to 2 seconds.
                Thread.sleep(wait);
                System.out.println("P1 Work done");
            }
    
        };
    
        Runnable p2 = new Runnable() {
    
            @Override
            public void run() {
                while (true) {
                    try {
                        // Do my work.
                        doWork();
                        // Grab the lock.
                        lock.lock();
                        // Do my update.
                        update();
                    } catch (InterruptedException ex) {
                        System.out.println("P2 Interrupted!");
                        break;
                    } finally {
                        lock.unlock();
                    }
                }
            }
    
            private void doWork() throws InterruptedException {
                long wait = random.nextInt(2000);
                System.out.println("P2 Working ... " + wait);
                // Wait up to 2 seconds.
                Thread.sleep(wait);
                System.out.println("P2 Work done");
            }
    
            private void update() throws InterruptedException {
                long wait = random.nextInt(2000);
                System.out.println("P2 Update ... " + wait);
                // Wait up to 2 seconds.
                Thread.sleep(wait);
                System.out.println("P2 Update done");
            }
    
        };
    
        // Create the two threads.
        Thread t1 = new Thread(p1);
        Thread t2 = new Thread(p2);
        // Start them up.
        t1.start();
        t2.start();
        // Wait 30 seconds - with narrative.
        for (int i = 0; i < 30; i++) {
            Thread.sleep(1000);
            System.out.println("Tick");
    
        }
        // Stop them.
        t1.interrupt();
        t2.interrupt();
        // Wait for them to stop.
        t1.join();
        t2.join();
    
    }
    

    运行此程序应该证明您的设计存在问题之一。请注意,P2 Update 很少被调用。这是因为P1 在释放锁上花费的时间很少,因此会使其他线程饿死。看看在p1 中它将如何lock.unlock() 然后几乎立即再次执行lock.lock(),因为它再次开始循环。

    【讨论】:

    • 嗨@OldCurmudgeon,如果doWork() 对以上2个Runnables很常见怎么办。谢谢。
    • 锁和使用同步块在这里不会有太大的不同,但正如您正确指出的那样,总体上的设计似乎有缺陷。
    • @AshishPatil - 为什么会有问题?只需将其移动到两个线程都可以访问的范围内。如果您愿意,您甚至可以将其创建为 lambda。
    • 实际上,您可以通过将true 传递给ReentrantLock 构造函数从而将其设置为公平模式来解决其他线程饥饿的问题。这应该允许发布锁的粗略fifo顺序,即p2不会被p1饿死,因为在某些时候p2会比p1等待更长的时间。
    【解决方案2】:

    一种方法是使用锁(查看java.util.concurrent.locks 包),但我首先会考虑是否可以改进一般方法,例如使用无锁算法等。

    另一种方法是在共享对象上使用synchronized 方法/块。然而,这一切都取决于你实际想要达到的目标。

    使用synchronized 块的示例:

    T1:

    while(...) {
      synchronized( SomeClass.class ) {
        doWork();
      }
    }
    

    T2:

    while(...) {
      doWork();
      synchronized( SomeClass.class ) {
        update();
      }
    }
    

    在这里,您将在 Class&lt;SomeClass&gt; 的同一实例上进行同步,只要您使用相同的类加载器,它就可以工作。

    请记住,您应该使同步块尽可能小,以免添加不必要的阻塞。

    除此之外,请注意 T1 中的同步块可能会使 T2 难以在两次迭代之间中断。然而,问题是为什么它是这样设计的。

    编辑

    作为同步整个 doWork() 调用的替代方法,您可能需要考虑实际需要同步的内容。

    以如下伪代码为例:

      WorkResult doWork(SharedObject so) {
        Data data = so.loadData();
        WorkResult wr = doSomeLengthyWork(data);
        return wr;
      }
    
      void update(WorkResult wr, SharedObject so) {
        so.updateFromWorkResult( wr );
      }
    

    如果你的情况是这样的,你可以同步调用so.loadData()so.updateFromWorkResult(),让冗长的操作在so.loadData()提供的数据副本上工作。

    编辑 2:

    您也可以使用ReadWriteLock 实现:

    T1:
    
    while(...) {
      Lock readlock = readWriteLock.readLock();
      readlock.lock();
    
      doWork();
    
      readlock.unlock();  
    }
    

    T2:

    while(...) {
      doWork();
    
      Lock writelock= readWriteLock.writeLock();
      writelock.lock();
    
      update();
    
      writelock.unlock();
    }
    

    请注意,为简单起见,我省略了异常处理等。

    您在这里所做的基本上是在工作期间获取读锁,在更新时获取写锁。除非有写锁(允许多个读锁),否则多个线程可以并行工作,并且更新将等到所有读锁都被释放。为了实现这一点,您可以在公平模式下使用ReentrantReadWriteLock,它应该按照请求的顺序发出锁,即当 T2 请求锁而 T1 仍在读取时,它将获得下一个锁,尽管 T1 立即请求再次锁定。

    【讨论】:

    • 这只有在我将 Thread.sleep() 放在 T1 中的同步块后面时才有效。
    • @doev 是的,这是真的,也是我写That all depends on what you're actually trying to achieve 的原因。如果没有更多信息,很难给出准确 的建议,上面的例子只是一个例子。不过我会添加更多信息。
    • “无锁”和“等待”这两个词不属于一起。如果一个线程无法取得进展不会阻止任何其他线程取得进展,则算法是“无锁”的。在这种情况下,明确要求两个线程轮流相互等待。任何时候一个线程等待另一个线程,根据定义,系统都不是无锁的。
    • 我必须纠正我。它在没有 sleep() 的情况下工作,但是 T2 大部分时间都在 T1 上等待。我同意,为了更深入的讨论,我必须编写一些示例代码。
    • @jameslarge 实际上,通过提到无锁方法,我是在质疑两个线程通常相互等待的要求。
    【解决方案3】:

    这是一个伪代码,真的,但我希望这个想法是可以理解的。

    ExecutorService es = ...;
    Future t1work = new Future(...);
    Future t2work = new Future(...);
    Future t2update = new Future(...);
    
    es.submit(t1work);
    es.submit(t2work);
    
    while (1) {
      if (t1work.isDone() && t2work.isDone())   es.submit(t2update);
      if (t1work.isDone() && t2update.isDone()) es.submit(t1work);
      if (t2work.isDone() && t2update.isDone()) es.submit(t2work);
      Thread.sleep(100);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-05
      • 2021-10-08
      • 2011-11-12
      • 2018-07-20
      • 2021-06-01
      • 2021-05-19
      相关资源
      最近更新 更多