方兆国 发表于 2013-07-18 10:56

Java虚拟机并发编程中运算结果不确定问题

@duanjigang @send_linux
@hbsycw @redcap0 @zavakid @gnah


Java虚拟机编程技术大家谈---多核和并发编程
我在这个活动中获得了《Java虚拟机并发编程 》图书1本.看了一下,照着书上的例子作了一下,发现了一个问题.

在书第18页的那个关于线程池的例子,我在原例子上略作了一些改动,并没有影响到核心问题,但是每次运行的结果都是不确定的

以下是我的测试代码

这个类是从服务器获取数据文件,例子中的网址是雅虎财经的,我现在打开那个连接已经无效了,因此使用本地的Nginx提供文件package com.fangzhaoguo.finance;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;

public class YahooFinance {

        final static int ID = 0;
        final static int Name = 1;

        private URL url;
        private BufferedReader reader;

        public YahooFinance(final String host) {
                try {
                        url = new URL(host);
                        reader = new BufferedReader(new InputStreamReader(url.openStream()));
                } catch (MalformedURLException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }

        public double getPrice(final String ticker, int way) {
                String data = null;
                String[] dataItem;
                double lastPrice = 0;

                try {
                        while (null != (data = reader.readLine())) {
                                dataItem = data.split(",");
                                if (ticker.equals(dataItem)) {
                                        lastPrice = Double.valueOf(dataItem);
                                        break;
                                }
                        }
                } catch (IOException e) {
                        e.printStackTrace();
                }
                return lastPrice;
        }

        public void close() {
                try {
                        reader.close();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }
}
这个类就是自己建立线程池来处理并发的请求,以前不是并发的时候完全没有问题,改成并发之后,每次运行的结果都不一样package com.fangzhaoguo.finance;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class YahooFinanceNAV {

        String host, filename;
        YahooFinance finance;
        Map<String, Integer> stocks;

        public YahooFinanceNAV(String host, String filename) {
                this.host = host;
                this.filename = filename;
                finance = new YahooFinance(host);
                stocks = new HashMap<String, Integer>();
        }

        public void readTickers() {
                try {
                        final BufferedReader reader = new BufferedReader(new FileReader(
                                        filename));
                        String info = null;
                        String[] infoItem;
                        while (null != (info = reader.readLine())) {
                                infoItem = info.split(",");
                                stocks.put(infoItem, Integer.valueOf(infoItem));
                        }
                        reader.close();
                } catch (FileNotFoundException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }

        private int getPoolSize() {
                return 4 + stocks.size() / Runtime.getRuntime().availableProcessors();
        }

        public double computeNAV() {
                double NAV = 0.0;
                int poolSize = getPoolSize();
                List<Callable<Double>> partitions = new ArrayList<Callable<Double>>();
                for (final String ticker : stocks.keySet()) {
                        partitions.add(new Callable<Double>() {
                                public Double call() throws Exception {
                                        return stocks.get(ticker)
                                                        * finance.getPrice(ticker, YahooFinance.ID);
                                }
                        });
                }

                ExecutorService executorService = Executors
                                .newFixedThreadPool(poolSize);
                try {
                        List<Future<Double>> valFutures = executorService.invokeAll(
                                        partitions, 10000, TimeUnit.SECONDS);
                        for (final Future<Double> vaFuture : valFutures) {
                                NAV += vaFuture.get();
                        }
                } catch (InterruptedException e) {
                        e.printStackTrace();
                } catch (ExecutionException e) {
                        e.printStackTrace();
                }
                executorService.shutdown();
                finance.close();
                return NAV;
        }
}
package com.fangzhaoguo.finance;

public class Main {

        public static void main(String[] args) {
                YahooFinanceNAV yahooFinanceNAV = new YahooFinanceNAV(
                                "http://localhost/table.csv", "stocks.dat");
                yahooFinanceNAV.readTickers();
                System.out.println(yahooFinanceNAV.computeNAV());
        }
}

craaazy123 发表于 2013-07-18 11:53

看看这样行不行?
for (final Future<Double> vaFuture : valFutures) {
      if(vaFuture.isDone()) {
            NAV += vaFuture.get();
       }
}

方兆国 发表于 2013-07-18 12:01

craaazy123 发表于 2013-07-18 11:53 static/image/common/back.gif
看看这样行不行?
for (final Future vaFuture : valFutures) {
      if(vaFuture.isDone()) {


额,谢谢你

gnah 发表于 2013-07-18 12:58

这不是线程安全的问题,而是线程执行顺序不确定

zavakid 发表于 2013-07-18 14:57

这样的场景,推荐使用 CompletionService ,然后用 take 来获得 future,这样可以最快拿到结果。

方兆国 发表于 2013-07-18 22:55

craaazy123 发表于 2013-07-18 11:53 static/image/common/back.gif
看看这样行不行?
for (final Future vaFuture : valFutures) {
      if(vaFuture.isDone()) {


我试了不可以

但是在另外一个例子中同样的方法是成功的.感觉这个问题是由于IO速度慢造成的,不过我是本机测试都这样,如果换成是直接从网上获取数据那不更慢

方兆国 发表于 2013-07-18 22:57

gnah 发表于 2013-07-18 12:58 static/image/common/back.gif
这不是线程安全的问题,而是线程执行顺序不确定

最后的数据结果不一致,应该是由数据丢失了,我做了一个没有磁盘IO的例子,不存在这个问题
我试一下用虚拟磁盘吧

方兆国 发表于 2013-07-18 23:08



这是用虚拟盘软件制作了一个虚拟磁盘,盘符是F:


将所有的文件都放在虚拟盘中,运算结果还是不一致


方兆国 发表于 2013-07-18 23:09

试一下楼上建议的CompletionService吧
感觉这个问题很怪异,我试了另外一个例子完全没有问题,是个算素数的,没有磁盘IO操作,是运算密集型的例子

方兆国 发表于 2013-07-18 23:40

回复 5# zavakid public double computeNAV() {
                double NAV = 0.0;
                int poolSize = getPoolSize();
                ExecutorService executorService = Executors
                                .newFixedThreadPool(poolSize);
                CompletionService<Double> completionService = new ExecutorCompletionService<Double>(
                                executorService);
                for (final String ticker : stocks.keySet()) {
                        completionService.submit(new Callable<Double>() {
                                public Double call() throws Exception {
                                        return stocks.get(ticker)
                                                        * finance.getPrice(ticker, YahooFinance.ID);
                                }
                        });
                }
                for (int i = 0; i < stocks.size(); i++) {
                        try {
                                NAV += completionService.take().get();
                        } catch (InterruptedException | ExecutionException e) {
                                e.printStackTrace();
                        }
                }
                executorService.shutdown();
                finance.close();
                return NAV;
        }我从网上看了一个例子,是这么个用法,可是运行起来还是老毛病,结果总是老变
页: [1] 2
查看完整版本: Java虚拟机并发编程中运算结果不确定问题