【问题标题】:Update a shared variable from all the threads从所有线程更新共享变量
【发布时间】:2018-03-15 21:50:52
【问题描述】:

我是多线程的新手。我有一个 volatile 变量 currentPrimeNo ,它将打印每个新线程的 run 方法中实现的下一个素数。但是每次我将每个线程的 currentPrimeNo 设为 0 时。我应该如何保持全局变量 currentPrimeNo 更新?

public class Processor implements Runnable {
    private int id;
    private static volatile int currentPrimeNo = 0;

    public Processor(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.println("Starting process id: " + id);
        currentPrimeNo = Utils.generateNextPrime(currentPrimeNo);
        System.out.println("Prime Number Associated with this thread is: " + currentPrimeNo);
        System.out.println("Completed process id: " + id);
    }

}

主要类是:

public class MainClass {

    @SuppressWarnings("resource")
    public static void main(String[] args) {
        System.out.println("****This is where the project starts*****");
        Scanner reader = new Scanner(System.in);
        System.out.print("Enter number of processes you want to create: ");
        int n = reader.nextInt();
        ExecutorService executor = Executors.newFixedThreadPool(n);
        for(int i=1;i<=n; i++) {
            executor.submit(new Processor(i));
        }
        executor.shutdown();
        try {
            executor.awaitTermination(10, TimeUnit.MINUTES);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println("****This is where the project ends*****");
    }
}

下面是 Util 类的 generateNextPrime 方法:

public synchronized static int generateNextPrime(int currentPrime) {
        int nextPrime = 2;
        if (currentPrime <= 1) {
            return nextPrime;
        } else {
            for (int i = currentPrime + 1;; i++) {
                boolean notPrime = false;
                for (int j = 2; j < i; j++) {
                    if (i % j == 0) {
                        notPrime = true;
                        break;
                    }
                }
                if (notPrime == false) {
                    return i;
                }
            }
        }
    }

下面是我得到的输出:

****这是项目开始的地方*****

输入要创建的进程数:4

启动进程id:2

启动进程id:3

启动进程id:1

启动进程id:4

与此线程相关的质数是:2

与此线程相关的质数是:2

完成的进程ID:4

完成的进程ID:1

与此线程相关的质数是:2

完成的进程ID:2

与此线程相关的质数是:2

完成的进程ID:3

****这是项目结束的地方*****

【问题讨论】:

  • 你怎么知道Utils.generateNextPrime 没有错误地返回0?你单独测试过吗?
  • 是的,我测试过,运行流畅。如果你传递它 0 它给出 2,如果你传递它 2 它给出下一个素数,即 3 等等。
  • 请显示最小但完整的示例(即minimal reproducible example),以便其他人可以重现您的错误。您到目前为止显示的代码不足以重现错误。
  • 我在原始问题的编辑中添加了主类。
  • 可以分享一下generateNextPrime的内部结构吗?

标签: java multithreading volatile java-threads thread-synchronization


【解决方案1】:

由于您没有在此处共享generateNextPrime 的代码,因此很难指出代码到底在哪里出错。

存在与此相关的固有问题。

在添加 Util.generateNextPrime() 后编辑。

当我们使用volatile 关键字时,所有线程都会看到当前值,而不是变量的缓存值。但是在您的代码中,volatile 变量是在 Runnable 实现中定义的。因此,它不是为此目的而服务的。确实,run 方法调用了generateNextPrime 并传递了volatile 变量,但被调用的方法实际上看到并处理的是变量的副本 和不是 exact 变量(阅读有关按值传递与按引用传递的更多信息将有助于更好地理解这一点)。这里的目的是拥有一个变量,其值应由generateNextPrime 调用更改,该调用将由每个线程在运行时完成。

我将 currentPrimeNo 定义移至 Util 类,以便所有线程只看到 一个 变量(而不是它的副本),这也是真实的-volatile 变量的时间值。为了简洁起见,generateNextPrime() 方法也做了一些改动。输出不一定需要按照相同的顺序,因为您不知道工作线程的调用顺序。

代码如下:

public class Processor implements Runnable {
    private int id;

    public Processor(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.println("Starting process id: " + id);
        int currentPrimeNo = Utils.generateNextPrime();
        System.out.println("Prime Number Associated with this thread " + id +" is: " + currentPrimeNo);
        System.out.println("Completed process id: " + id);
    }

}

public class Utils {

    private static volatile int currentPrime = 0;
    public static synchronized int generateNextPrime(){
        currentPrime++;
        if(currentPrime < 2){
            currentPrime = 2;
            return currentPrime;
        }
        for (int i = 2; i <currentPrime; i++) {
            if(currentPrime%i == 0) {
                currentPrime++;
                i=2;
            } else{
                continue;
            }
        }
        return currentPrime;
    }
}

基准测试时看到的输出

样本 1:

****This is where the project starts*****
Enter number of processes you want to create: 4
Starting process id: 3
Starting process id: 1
Starting process id: 2
Starting process id: 4
Prime Number Associated with this thread 3 is: 2
Prime Number Associated with this thread 1 is: 7
Completed process id: 1
Prime Number Associated with this thread 2 is: 3
Completed process id: 2
Prime Number Associated with this thread 4 is: 5
Completed process id: 3
Completed process id: 4
****This is where the project ends*****

示例 2:

****This is where the project starts*****
Enter number of processes you want to create: 6
Starting process id: 5
Starting process id: 1
Starting process id: 3
Starting process id: 2
Starting process id: 4
Prime Number Associated with this thread 2 is: 7
Prime Number Associated with this thread 4 is: 11
Completed process id: 4
Prime Number Associated with this thread 1 is: 3
Completed process id: 1
Prime Number Associated with this thread 5 is: 5
Completed process id: 5
Prime Number Associated with this thread 3 is: 2
Starting process id: 6
Completed process id: 2
Prime Number Associated with this thread 6 is: 13
Completed process id: 6
Completed process id: 3
****This is where the project ends*****

【讨论】:

  • 我已经添加了 generateNextPrime 方法。
  • 谢谢。这行得通,我明白出了什么错误。
  • 在您的固定代码中,currentPrime 不再需要成为volatile,因为整个访问权限都由synchronized 保护。
【解决方案2】:

澄清问题后:

a) 结果为零 - 事实上并非如此,我这次实际上已经运行了代码 :) 它按预期返回总随机结果,具体取决于创建的线程数。 (请注意,您使用的是 threads,而不是 processes。) 随机结果的原因是每个线程实例都是从其他线程实例设置的值开始的。由于执行顺序不确定,因此输出也不确定。

b) 没有一个接一个地生成素数 - 这是因为计算从多个线程开始同时,并且这些线程并行工作(这就是池执行器所做的) .

要强制所有任务按顺序运行,请使用 newSingleThreadExecutor。

// final ExecutorService executor = Executors.newFixedThreadPool(n); // this uses a pool
final ExecutorService executor = Executors.newSingleThreadExecutor(); // this is sequential 


public static void main(String[] args) throws InterruptedException {
    System.out.println("****This is where the project starts*****");
    final Scanner reader = new Scanner(System.in);
    System.out.print("Enter number of processes you want to create: ");
    final int n = reader.nextInt();
    // final ExecutorService executor = Executors.newFixedThreadPool(n); // this uses a pool
    final ExecutorService executor = Executors.newSingleThreadExecutor(); // this uses a single thread

    for(int i=1;i<=n; i++) {
        executor.submit(new Processor(i));
    }
    executor.awaitTermination(10, TimeUnit.MINUTES);
    System.out.println("****This is where the project ends*****");
}

预期的输出如下:

****这是项目开始的地方***** 输入您要创建的进程数:10 启动进程 id:1 关联的素数 这个线程是:2 完成的进程 id:1 开始进程 id:2 与此线程关联的素数是:3 已完成的进程 id:2 启动进程 id:3 与此线程关联的质数是:5 已完成的进程 id:3 开始进程 id:4 关联的素数 与此线程是:7 完成的进程 id:4 启动进程 id:5 与此线程关联的素数是:11 已完成的进程 ID: 5 启动进程 id:6 与此线程相关的质数是: 13 已完成进程 id:6 开始进程 id:7 质数 与此线程相关的是:17 Completed process id: 7 Starting 进程 ID:8 与此线程关联的质数是:19 已完成的进程 id:8 开始进程 id:9 关联的素数 这个线程是:23 完成的进程 id:9 启动进程 id: 10 与此线程相关的质数是:29 已完成的过程 编号:10

请注意,由于执行实际上是序列化的,因此在此处使用执行器(或单独的执行线程)不会获得性能提升。

可以从并行执行中受益的最佳问题是输入可以被拆分,然后由多个线程并行处理,最后再次组合回来的问题。例如,将位图图片转换为黑白对于并行执行来说是一个很好的问题,因为位图可以被分割成 8 块,这些块馈送到并行运行的 8 个线程。最后,一旦所有线程都完成,代码可以将输出组装成一张图片,并受益于 8 倍的执行速度。

【讨论】:

  • 谢谢。但是您的代码的唯一问题是线程没有并行运行。我希望线程并行运行。正如您在我的输出中看到的那样,这很好,只是问题是我希望为它们分配不同的素数,我不在乎线程运行的顺序。
  • 嗯,这来自您要解决的问题。您可以使用并行线程生成任意素数。您不能使用并行线程生成相互依赖的素数,因为一个线程需要另一个线程的结果才能开始任何计算。因此“计算所有个素数平行”的问题是不可能的。
【解决方案3】:

查看输出时,您可以看到所有 4 个线程都在任何计算之前启动。质数是在线程启动后立即计算的,因此很可能所有线程都以相同的起始质数 (0) 开始,因此以相同的结束数 (2) 结束。因此,您得到的输出是有意义的。

您的代码最大的问题是您并行执行计算,但期望按顺序获得结果。要实现所需的输出,可以将 Utils.generateNextPrime(currentPrimeNo) 方法调用包装在同步块中。这将确保一次只有一个线程可以对质数采取行动。

更新 1:这是我在运行您的代码时得到的输出:

****This is where the project starts*****
Enter number of processes you want to create: 4
Starting process id: 2
Prime Number Associated with this thread is: 2
Completed process id: 2
Starting process id: 1
Prime Number Associated with this thread is: 3
Completed process id: 1
Starting process id: 4
Prime Number Associated with this thread is: 5
Completed process id: 4
Starting process id: 3
Prime Number Associated with this thread is: 7
Completed process id: 3
****This is where the project ends*****

更新 2:您还可以如下更改您的处理器类,而不同步 generateNextPrime 方法:

public class Processor implements Runnable {
    private static Object lock = new Object();
    private int id;
    private static volatile int currentPrimeNo = 0;

    public Processor(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.println("Starting process id: " + id);
        synchronized (lock) {
            currentPrimeNo = generateNextPrime(currentPrimeNo);
        }
        System.out.println("Prime Number Associated with this thread is: " + currentPrimeNo);
        System.out.println("Completed process id: " + id);
    }
}

【讨论】:

  • 我现在已经同步了这个方法,但它仍然没有给出想要的输出。
  • 同步方法没有帮助,因为每个线程将使用不同的处理器对象锁定。您可以在 MainClass 中创建一个对象并使用该对象锁定方法调用: synchronized(object) {/* your method invocation */}.
  • 其实有帮助。处理器类中未声明 generateNextPrime 方法。我已经添加了我在答案中得到的输出。
  • 谢谢,现在可以使用了。但我有一个问题。当我将方法 generateNextPrime 放在 Util 类中时,它不起作用,但是当我将它放在 Processor 类中时,它起作用了。是不是因为Processor类实现了Runnable?
  • 这是否意味着所有线程安全方法都必须在实现可运行或扩展线程的类中?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-14
  • 2018-08-28
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-09
相关资源
最近更新 更多