【问题标题】:Understanding Multi-Threading in Java了解 Java 中的多线程
【发布时间】:2015-04-27 16:47:33
【问题描述】:

我正在学习 Java 中的 multithreading。问题陈述是:假设有一个数据结构可以包含数百万个整数,现在我想在其中搜索一个键。我想使用 2 个线程,这样如果任何一个线程找到密钥,它应该将共享布尔变量设置为 false,并且两个线程都应该停止进一步处理。

这是我正在尝试的:

public class Test  implements Runnable{
    private List<Integer> list;
    private Boolean value;
    private int key = 27;

    public Test(List<Integer> list,boolean value) {
        this.list=list;
        this.value=value;
    }

    @Override
    public void run() {
        synchronized (value) {
            if(value){
                Thread.currentThread().interrupt();
            }
            for(int i=0;i<list.size();i++){
                if(list.get(i)==key){
                    System.out.println("Found by: "+Thread.currentThread().getName());
                    value = true;
                    Thread.currentThread().interrupt(); 
                }
                System.out.println(Thread.currentThread().getName() +": "+ list.get(i));
            }
        }

    }
}

主要课程是:

public class MainClass {


    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>(101);
        for(int i=0;i<=100;i++){
            list.add(i);
        }

        Boolean value=false;

        Thread t1 = new Thread(new Test(list.subList(0, 49),value));
        t1.setName("Thread 1");

        Thread t2 = new Thread(new Test(list.subList(50, 99),value));
        t2.setName("Thread 2");

        t1.start();
        t2.start();
    }
}

我的期望:

两个线程都会随机运行,当任何一个线程遇到 27 时,两个线程都会被中断。所以,线程 1 不应该能够处理所有的输入,线程 2 类似。

但是,发生了什么:

两个线程都在完成循环,线程 2 总是在线程 1 完成后启动。

Please highlight the mistakes,我还在学习线程。

我的下一个练习题是:一个一个地访问任何共享资源

【问题讨论】:

  • 你为什么用synchronized
  • "shared boolean variable" 建议您可能想在相关的地方使用static 关键字...
  • @MuratK。因为boolean 是共享资源?这不是正确的方法吗??
  • @JonK AtomicBoolean 不在这里工作吗?
  • 也许static volatile boolean 会起作用。

标签: java multithreading concurrency


【解决方案1】:

您将整个代码块包装在对象value 下的synchronized 块下。这意味着,一旦执行到达同步块,第一个线程将把监视器保存到对象value 并且任何后续线程都将阻塞,直到监视器被释放 .

注意整个区块如何:

synchronized (value){
    if(value){
        Thread.currentThread().interrupt();
    }
    for(int i=0; i < list.size(); i++){
        if(list.get(i) == key){
            System.out.println("Found by: "+Thread.currentThread().getName());
            value = true;
            Thread.currentThread().interrupt(); 
        }
        System.out.println(Thread.currentThread().getName() +": "+ list.get(i));
    }
}

被包装在一个同步块中,这意味着只有一个线程可以一次运行该块,这与您的目标相反。

在这种情况下,我相信您误解了同步和“共享变量”背后的原则。澄清:

static - 是变量修饰符,用于使变量在对象之间成为全局变量(即类变量),以便每个对象共享相同的静态变量。

volatile - 是用于使变量线程安全的变量修饰符。请注意,您仍然可以在没有此修饰符的情况下从不同的线程访问变量(但这很危险并且可能导致竞争条件)。线程对变量的范围没有影响(除非您使用 ThreadLocal)。

我想补充一点,您不能将volatile 放在任何地方,并期望代码是线程安全的。我建议您阅读Oracle's guide on synchronization,以更深入地了解如何建立线程安全。

在您的情况下,我将删除同步块并将共享布尔值声明为:

private static volatile Boolean value;

此外,您现在尝试执行的任务是构建 Fork/Join 池的目的。我建议 reading this Oracle 的 java 教程的一部分,以了解如何在分而治之的方法中使用 Fork/Join 池。

【讨论】:

    【解决方案2】:

    通过将线程的主要逻辑包装在同步块中,该块中代码的执行变得互斥。线程 1 将进入块,获取“值”上的锁并运行整个循环,然后返回锁并允许线程 2 运行。

    如果您只包装标志“值”的检查和设置,那么两个线程应该同时运行代码。

    编辑:正如其他人所讨论的那样,在 Test 类中将“值”设为 static volatile boolean,并且根本不使用同步块,也可以。这是因为对 volatile 变量的访问就像在同步块中一样。

    参考:https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

    【讨论】:

    • 我猜使用volatile boolean,我必须让线程同时运行。但是,我将如何分享它?
    • 访问/修改一个 volatile 变量就像它被一个同步块包围一样。您有两个选择: 1. 继续使用synchronized 块,但仅限于值被修改或访问的地方。 2. 将 value 设为 Test 的 volatile static 成员并移除同步块
    【解决方案3】:

    您不应该在found 标志上获得锁定 - 这只会确保只有一个线程可以运行。而是设置标志 static 以便共享,volatile 使其不能被缓存。

    另外,您应该更频繁地检查标志。

    private List<Integer> list;
    private int key = 27;
    private static volatile boolean found;
    
    public Test(List<Integer> list, boolean value) {
        this.list = list;
        this.found = value;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < list.size(); i++) {
            // Has the other thread found it?
            if (found) {
                Thread.currentThread().interrupt();
            }
            if (list.get(i) == key) {
                System.out.println("Found by: " + Thread.currentThread().getName());
                // I found it!
                found = true;
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() + ": " + list.get(i));
        }
    }
    

    顺便说一句:您的两个线程都从 0 开始并沿数组向上 - 我假设您在此代码中执行此操作作为演示,并且您要么让它们从相反的两端工作,要么它们随机走动。

    【讨论】:

    • 两个线程都在运行,直到完成执行。他们没有被打断,循环正在完成
    • if(Thread.currentThread().isInterrupted()) { System.out.println("线程中断\n退出...");休息;为什么我需要添加这些?为什么调用interrupt方法时执行没有停止?
    • @VivekVardhan - 由于run 不能引发中断,您必须自己检查中断。查看interrupt() 的文档,它所做的只是设置一个标志。
    【解决方案4】:

    将布尔值设为静态,以便两个线程都可以访问和编辑同一个变量。然后你就不需要传递它了。然后一旦一个线程将它更改为 true,第二个线程也将停止,因为它使用相同的值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-11
      • 2012-01-16
      相关资源
      最近更新 更多