停放正在使用的线程

2024-04-20

我正在尝试线程停放并决定构建某种服务。它是这样的:

public class TestService {
    private static final Logger logger = LoggerFactory.getLogger(TestService.class); // logback I think this logger causes some troubles

    private final CountDownLatch stopLatch;
    private final Object parkBlocker = new Object();
    private volatile boolean stopped;
    private final Thread[] workers;

    public TestService(int parallelizm) {
        stopLatch = new CountDownLatch(parallelizm);
        workers = new Thread[parallelizm];
        for (int i = 0; i < parallelizm; i++) {
            workers[i] = new Thread(() -> {
                try {
                    while (!stopped) {
                        logger.debug("Parking " + Thread.currentThread().getName());
                        LockSupport.park(parkBlocker);
                        logger.debug(Thread.currentThread().getName() + " unparked");
                    }
                } finally {
                    stopLatch.countDown();
                }
            });
        }
    }

    public void start() {
        Arrays.stream(workers).forEach(t -> {
            t.start();
            logger.debug(t.getName() + " started");
        });
    }

    public boolean stop(long timeout, TimeUnit unit) throws InterruptedException {
        boolean stoppedSuccefully = false;
        this.stopped = true;
        unparkWorkers();
        if (stopLatch.await(timeout, unit)) {
            stoppedSuccefully = true;
        }
        return stoppedSuccefully;
    }

    private void unparkWorkers() {
        Arrays.stream(workers).forEach(w -> {
            LockSupport.unpark(w);
            logger.debug("Un-park call is done on " + w.getName());
        });
    }
}

我面临的问题是,如果我按如下方式测试该服务:

public static void main(String[] args) = {
  while(true) {
    TestService service = new TestService(2);
    service.start();
    if (!service.stop(10000, TimeUnit.MILLISECONDS))
      throw new RuntimeException();
  }
}

我有时会出现以下行为:

14:58:55.226 [main] DEBUG com.pack.age.TestService - Thread-648 started
14:58:55.227 [Thread-648] DEBUG com.pack.age.TestService - Parking Thread-648
14:58:55.227 [main] DEBUG com.pack.age.TestService - Thread-649 started
14:58:55.227 [main] DEBUG com.pack.age.TestService - Un-park call is done on Thread-648
14:58:55.227 [Thread-648] DEBUG com.pack.age.TestService - Thread-648 unparked
14:58:55.227 [main] DEBUG com.pack.age.TestService - Un-park call is done on Thread-649
14:58:55.227 [Thread-649] DEBUG com.pack.age.TestService - Parking Thread-649
Exception in thread "main" java.lang.RuntimeException
    at com.pack.age.Test$.main(Test.scala:12)
    at com.pack.age.Test.main(Test.scala)

该线程在停车时悬而未决:

"Thread-649" #659 prio=5 os_prio=0 tid=0x00007efe4433f000 nid=0x7691 waiting on condition [0x00007efe211c8000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x0000000720739a68> (a java.lang.Object)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at com.pack.age.TestService.lambda$new$0(TestService.java:27)
    at com.pack.age.TestService$$Lambda$1/1327763628.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

我在服务中没有看到任何停车-取消停车的比赛。此外,如果unpark之前被调用park, the park保证不会阻塞(javadocs 就是这么说的)。

也许我误用了LockSupport::park。你能提出任何修复建议吗?


这与记录器无关,尽管它的使用使问题浮出水面。你有一个竞争条件,就这么简单。在解释竞争条件之前,您需要了解一些事情LockSupport::unpark首先是文档:

使给定线程的许可可用(如果该许可尚不可用)。如果线程在 Park 时被阻塞,那么它将解除阻塞。否则,保证其下一次对 Park 的调用不会被阻塞。

第一点解释一下here https://stackoverflow.com/questions/7497793/understanding-java-lang-thread-state-waiting-parking/51788005#51788005。简短的版本是:如果你有thread这已经开始了,但是还没有 called park,并在这段时间内(之间start线程和park),一些其他线程调用unpark关于第一个:该线程根本不会停顿。许可证将立即可用。也许这张小图会让它更清楚:

(ThreadA)  start ------------------ park --------- ....

(ThreadB)  start ----- unpark -----

注意如何ThreadB calls unpark(ThreadA)期间之间ThreadA已致电start and park。因此,当ThreadA达到park: 保证不阻塞,正如文档所说。

同一文档的第二点是:

如果给定线程尚未启动,则不能保证此操作有任何效果。

我们通过一张图来看看:

Thread B calls unpark(ThreadA) --- Thread A starts --- Thread A calls park 

After ThreadA calls park,它将永远挂起,因为ThreadB从不打电话unpark再次关于它。请注意,调用unpark被做了before ThreadA已经开始(与前面的示例不同)。

这正是您的情况所发生的情况:

LockSupport.unpark(w); (from unparkWorkers) 叫做before t.start(); from public void start(){...}。简而言之 - 您的代码调用unpark双方workers before他们甚至开始,当他们最终到达时park- 他们被困住了,没有人能够unpark他们。事实上,你看到这个logger并且不与System::out最有可能与你的脸有关println- 有一个synchronized方法在幕后。


事实上,LockSupport准确地提供了证明这一点所需的语义。为此我们需要(为简单起见:SOProblem service = new SOProblem(1);)

static class ParkBlocker {

    private volatile int x;

    public ParkBlocker(int x) {
        this.x = x;
    }

    public int getX() {
        return x;
    }
}

现在我们需要将其插入到正确的方法中。首先标记我们已经调用的事实unpark:

private void unparkWorkers() {
    Arrays.stream(workers).forEach(w -> {
        LockSupport.unpark(w);
        logger.debug("Un-park call is done on " + w.getName());
    });
    /*
     * add "1" to whatever there is already in pb.x, meaning
     * we have done unparking _also_
     */
    int y = pb.x;
    y = y + 1;
    pb.x = y;
}

然后在一个周期结束后重置标志:

public boolean stop(long timeout, TimeUnit unit) throws InterruptedException {
    boolean stoppedSuccefully = false;
    stopped = true;
    unparkWorkers();
    if (stopLatch.await(timeout, unit)) {
        stoppedSuccefully = true;
        // reset the flag
        pb.x = 0;
    }
    return stoppedSuccefully;
}

然后更改构造函数以标记线程已启动:

  .....
  while (!stopped) {
       logger.debug("Parking " + Thread.currentThread().getName());
       // flag the fact that thread has started. add "2", meaning
       // thread has started
       int y = pb.x;
       y = y + 2;
       pb.x = y;
       LockSupport.park(pb);
       logger.debug(Thread.currentThread().getName() + " unparked");
  }

然后,当线程冻结时,您需要检查该标志:

 public static void main(String[] args) throws InterruptedException {
    while (true) {
        SOProblem service = new SOProblem(1); // <-- notice a single worker, for simplicity
        service.start();
        if (!service.stop(10000, TimeUnit.MILLISECONDS)) {
            service.debug();
            throw new RuntimeException();
        }
    }
}

where debug方法是:

public void debug() {
    Arrays.stream(workers)
          .forEach(x -> {
              ParkBlocker pb = (ParkBlocker) LockSupport.getBlocker(x);
              if (pb != null) {
                  System.out.println("x = " + pb.getX());
              }
          });
}

当问题再次出现时,您已致电unpark before你打过电话了park,这发生在当x = 3作为输出。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

停放正在使用的线程 的相关文章

  • spring - 强制 @Autowired 字段的 cglib 代理

    我有混合堆栈 EJB 和 Spring 为了将 Spring 自动装配到 EJB 我使用SpringBeanAutowiringInterceptor 不确定这是否会影响我遇到的问题 在尝试通过以下方式自动装配 bean 时 Scope p
  • Java替换特定字符

    这是我在这个网站上的第一个问题 所以我会尽量不要成为一个十足的菜鸟 我目前正在用java 创建刽子手游戏 所以我问你的问题是我们是否被赋予了 幽灵 这个词 并将 Ghost 替换为 hiddenWord ghost length for i
  • cucumber-junit-platform-engine 中的功能文件发现

    In cucumber junit我使用的库 CucumberOptions定义功能文件位置 package com mycompany cucumber import cucumber api CucumberOptions import
  • 参数动态时如何构建 JPQL 查询?

    我想知道是否有一个好的解决方案来构建基于过滤器的 JPQL 查询 我的查询太 富有表现力 我无法使用 Criteria 就像是 query Select from Ent if parameter null query WHERE fiel
  • 具有多种值类型的 Java 枚举

    基本上我所做的是为国家编写一个枚举 我希望不仅能够像国家一样访问它们 而且还能够访问它们的缩写以及它们是否是原始殖民地 public enum States MASSACHUSETTS Massachusetts MA true MICHI
  • 覆盖 MATLAB 默认静态 javaclasspath 的最佳方法

    MATLAB 配置为在搜索用户可修改的动态路径之前搜索其静态 java 类路径 不幸的是 静态路径包含相当多非常旧的公共库 因此如果您尝试使用新版本 您可能最终会加载错误的实现并出现错误 例如 静态路径包含 google collectio
  • 从 html 页面和 javascript 调用 java webservice

    我正在尝试从 javascript 调用 java 实现的 Web 服务 使用 NetBeans IDE 我读过很多关于 jQuery 和 AJAX 的内容 但我似乎无法掌握它 假设我的 Web 服务 WSDL 位于 http localh
  • 从 Java 日历迁移到 Joda 日期时间

    以前 当我第一次设计股票应用相关软件时 我决定使用java util Date表示股票的日期 时间信息 后来我体会到了大部分方法java util Date已弃用 因此 很快 我重构了所有代码以利用java util Calendar 然而
  • 让JScrollPane控制多个组件

    对于我的应用程序 我正在设计一个脚本编辑器 目前我有一个JPanel其中包含另一个JPanel保存行号 位于左侧 以及JTextArea用于允许用户输入代码 位于右侧 目前 我已经实施了JScrollPane on the JTextAre
  • 从 Stax XMLStreamReader 读取以解组部分

    我正在使用 Stax 游标 API 从大型 xml 文件中提取数据 当前 我转到特殊标签的开头并使用 JAXB 解组该标签 这对于格式良好的 xml 文件效果很好 但不久前我有一个文档 其中数十万个标签中有一个未关闭 JAXB 使用 XML
  • 阻止 OSX 变音符号为所有用户禁用 Java 中的 KeyBindings?

    注 我知道这个问题 https stackoverflow com questions 40335285 java keybinds stop working after holding down a key用户必须输入终端命令才能解决此问
  • 如何移动图像(动画)?

    我正在尝试在 x 轴上移动船 还没有键盘 我如何将运动 动画与boat png而不是任何其他图像 public class Mama extends Applet implements Runnable int width height i
  • struts 教程或示例

    我正在尝试在 Struts 中制作一个登录页面 这个想法是验证用户是否存在等 然后如果有错误 则返回到登录页面 错误显示为红色 典型的登录或任何表单页面验证 我想知道是否有人知道 Struts 中的错误管理教程 我正在专门寻找有关的教程 或
  • 从一个文本文件中获取数据并将其移动到新的文本文件

    我有一个文件 里面有数据 在我的主要方法中 我读入文件并关闭文件 我调用另一种方法 在原始文件的同一文件夹内创建一个新文件 所以现在我有两个文件 原始文件和通过我调用的方法生成的文件 我需要另一种方法 从原始文件中获取数据并将其写入创建的新
  • 使用 eclipse IDE 配置 angularjs

    我想开始使用 AngularJs 和 Java Spring 进行开发 我使用 Eclipse 作为 IDE 我想配置我的 Eclipse 以使这些框架无缝工作 我知道我可能要求太多 但相信我 我已经做了很多研究 你们是我最后的选择 任何帮
  • 字符串的外部文件,而不是对它们进行硬编码

    我有开发一些 Android 应用程序的经验 这些应用程序可以轻松地拥有可以存储字符串的 XML 字符串文件 上次我开发桌面 Java 应用程序时 我对字符串进行了硬编码 例如按钮标题 标签等 我后来了解到这是不好的做法 我应该将字符串存储
  • Apache Commons exec PumpStreamHandler 连续输入

    我正在尝试使用 Apache Commons exec 解决与命令行进程的交互 我被以下代码困住了 ByteArrayOutputStream out new ByteArrayOutputStream ByteArrayOutputStr
  • 如何使用 sapjco3 和 Eclipse 连接到 SAP 系统?

    我需要通过标准 BAPI 调用连接到 SAP 系统 我已经安装了 JCo sapjco3 并将 jar 添加到 Eclipse 中的构建路径中 但由于我是网络 服务器编程的初学者 我不知道如何在 Eclipse 和 SAP 系统之间建立连接
  • 是否有 JavaScript 正则表达式相当于 Java 正则表达式中的交集 (&&) 运算符? [复制]

    这个问题在这里已经有答案了 在 Java 正则表达式中 您可以使用交集运算符 在字符类中以简洁地定义它们 例如 a z def d e or f a z bc a through z except for b and c JavaScrip
  • 使用 JLayer 调整音量

    我和一个朋友正在将 MP3 播放器编程作为学校项目 我们即将完成 现在陷入了尝试编写一个函数来改变播放器音量的阶段 我们正在使用 音频设备 高级玩家 我知道其他人已经问过同样的问题 但我没有完全得到解决方案 而且我不想回答这么老的问题 所以

随机推荐