【问题标题】:static volatile boolean - thread not getting terminatedstatic volatile boolean - 线程没有被终止
【发布时间】:2014-04-03 08:40:35
【问题描述】:

我写了一个简单的多线程应用程序,只是为了玩并发,但我有一个布尔变量的问题,它控制线程中的循环。如果队列中没有剩余元素,其中一个函数应该停止线程,我想这是我的问题,因为如果我在大括号之间添加一些东西:

while (!queue.isEmpty()) {
}
isRunning = false;

所以它变成了:

while (!queue.isEmpty()) {
    System.out.println("ASD");
}
isRunning = false;

它工作得更好了——程序在执行turnOff方法后终止

有什么想法吗?

这是我的应用程序的完整代码:

package test;

public class xxx {
    public static void main(String[] args) {
        Foo instance = Foo.getInstance();
        Thread x = new Thread(instance);
        x.start();

        for (int count = 1; count < 100000; count++)
            instance.addToQueue(count + "");
        instance.turnOff();
    }
}

还有:

package test;

import java.util.LinkedList;
import java.util.List;

public class Foo implements Runnable {
    private static Foo inner = null;
    private static List<String> queue = new LinkedList<String>();
    private volatile static boolean isRunning = false;

    private Foo() { }

    public static Foo getInstance() {
        if (inner == null) {
            inner = new Foo();
        }
        return inner;
    }

    public void addToQueue(String toPrint) {
        synchronized (queue) {
            queue.add(toPrint);
        }

    }

    public void removeFromQueue(String toRemove) {
        synchronized (queue) {
            queue.remove(toRemove);
        }
    }

    public void turnOff() {
        while (!queue.isEmpty()) {
        }
        System.out.println("end");
        isRunning = false;
    }

    @Override
    public void run() {
        isRunning = true;
        while (isRunning) {
            if (!queue.isEmpty()) {
                String string = queue.get(0);
                System.out.println(string);
                removeFromQueue(string);
            }

        }
    }
}

【问题讨论】:

  • 你知道while (!queue.isEmpty()) {} 会做什么吗?它将消耗所有您的 CPU 资源,因为它将不断检查单个标志/位/值的状态,该标志/位/值很可能存储在 CPU 寄存器中。通常你会想要添加一个 `wait();'或循环中的类似语句
  • 编译器优化你的代码并踢出空循环,因为他认为它没有做任何事情
  • 请看我的帖子。
  • 相反,您链接的问题与我的问题重复,因为它是 4 天前提出的,截至目前

标签: java multithreading concurrency


【解决方案1】:

这是一个竞争条件问题。可能 run 方法(另一个线程)在主线程中的 turnOff 之后执行,因此标志 isRunning 再次设置为 true 并且循环永远不会结束。

这可以解释为什么使用简单的 System.out.println("ASD") 会变得更好:isRunning=false 会延迟。

【讨论】:

  • 将 x.start() 移动到 for 循环之后也应该有所帮助。
【解决方案2】:

你的代码有很多问题。

  1. turnOffwait 中的繁忙循环
  2. turnOffrun 中对queue 的非同步访问
  3. inner 的非易失性、非最终访问
  4. 不必要的静态 isRunningqueue 变量
  5. turnOffstart 调用之间的竞争条件

其中一些在这个特定的实例中是无害的(例如,instance 总是从主线程访问),但根据您的硬件配置,您可能会被其余的一些组合所困扰。添加System.out“修复”问题的原因是它使繁忙循环之一变得不那么繁忙(修复1)并具有内部同步机制(修复2),但其他循环仍然存在。

我建议去掉isRunning 变量和queue.isEmpty() 的测试并替换为CountDownLatch

package test;

import java.util.LinkedList;
import java.util.List; 
import java.util.concurrent.CountDownLatch;

public class Foo implements Runnable {
    private static final Foo inner = new Foo();
    private final List<String> queue = new LinkedList<String>();
    private final CountDownLatch latch = new CountDownLatch(1);

    private Foo() { }

    public static Foo getInstance() {
        return inner;
    }

    public void addToQueue(String toPrint) {
        synchronized (queue) {
            queue.add(toPrint);
        }
    }

    public void removeFromQueue(String toRemove) {
        synchronized (queue) {
            queue.remove(toRemove);
        }
    }

    public boolean isEmpty() {
        synchronized (queue) {
            return queue.isEmpty();
        }
    }

    public String getHead() {
        synchronized (queue) {
            return queue.get(0);
        }
    }

    public void turnOff() throws InterruptedException {
        latch.await();
        System.out.println("end");
    }

    @Override
    public void run() {
        while (!isEmpty()) {
            String string = getHead();
            System.out.println(string);
            removeFromQueue(string);
        }

        latch.countDown();
    }
}

还有跑步者

package test;

public class XXX {
    public static void main(String[] args) throws InterruptedException {
        Foo instance = Foo.getInstance();
        Thread x = new Thread(instance);

        for (int count = 1; count < 100000; count++)
            instance.addToQueue(count + "");

        x.start();
        instance.turnOff();
    }
}   

【讨论】:

  • 我认为 Isidro 确定了代码失败的正确具体原因,但这是一个很好的总体指导性答案。我认为只使用其中一种并发列表实现会容易得多(例如ConcurrentLinkedQueue 或者LinkedBlockingDeque,我还没有仔细检查)。
【解决方案3】:

主要问题是添加/删除元素和检查队列是否为空之间的竞争条件。更多的话:

addremove 调用包装在synchronized 块中可以保证这些方法的所有调用都将按顺序执行。但是,在synchronized 块之外还有一个对queue 变量的访问——它是queue.isEmpty()。这意味着某个线程有可能会获得此调用的结果,并且当它在 if 块内执行操作时,其他线程可能会添加或删除元素。

这段代码还有一些并发问题,如果你想讨论它们请告诉我(它们有点跑题了)。

【讨论】:

    【解决方案4】:

    作为 Germann Arlington 的观点,queue.isEmpty() 的值似乎被缓存在主线程中。尝试同步它:

    while (true) {
        synchronized(queue) {
            if(queue.isEmpty())
                break;
        }
    } 
    

    或者只是让队列变得易变:

    private volatile static List<String> queue = new LinkedList<String>();
    

    【讨论】:

    • 不是关于缓存值,而是关于连续访问它
    • 使队列不稳定不是正确的答案(即使它似乎有帮助)。变化的不是变量本身,而是它所引用的对象的状态。
    • 我承认 volatile 的解决方案并不正确。正如任何帖子中提到的那样,添加到此循环的任何操作都可以解决问题。这就是为什么我认为整个 queue.isEmpty() 都被缓存了,以后不会检查。 volatile让JVM每次都执行isEmpty操作。
    【解决方案5】:

    这将解决您的问题。

    在 turnOff() 方法的 while 循环中也使用 volatile 变量 isRunning。

    public void turnOff() {
        while (isRunning && !queue.isEmpty()) {
        }
        System.out.println("end");
        isRunning = false;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-11
      • 2019-12-21
      • 1970-01-01
      • 1970-01-01
      • 2014-07-12
      • 1970-01-01
      相关资源
      最近更新 更多