【发布时间】:2014-05-12 06:32:45
【问题描述】:
我有一个多线程 Java 程序出现内存一致性错误。我天真地试图通过声明我所有的变量 volatile 和同步方法来解决这个问题,但这并没有奏效。
有时,前一个对象的内存会出现在其他新对象中。这是真实世界的输出:
<Thread-13> OptionRow (AMGN_062714P116) expDate set to 2014-06-27
Thread-9 2) OFT-Thread (AIG_052314C50) populateTable expDate = 2014-06-27 year=2014 month=5 day=23
<Thread-9> OptionRow (AIG_052314C50) expDate set to 2014-05-23
您可以看到线程 9 将 expDate 设置为与线程 13 设置的值相同的值。不过那是错误的,不知何故,正确的值赶上了。把这个小代码sn-p做起来很难,所以我会尽量准确描述代码序列,同时保持简短。
编辑:我修改了每个请求的代码(原始代码太短)
这段代码仍然缺少一些东西。我不能包含它,因为它允许您连接到 TDAmeritrade 的 API 服务器。如果这样会更好,我可以创建随机生成的日期吗?我知道这不会为你编译。我仍在学习该站点的协议。让我知道这是否有误。
此外,当我将其作为单线程运行时,它可以工作。没有内存错误。所以,肯定是多线程。此版本与我最初发布的版本不同。我希望使用 ExecutorService 线程池会有所帮助。它没有帮助。
import java.util.Iterator;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import broker.Root;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import networking.ParseBinaryOptionChain;
public class StackOverflowCode {
public StackOverflowCode () {
Root root = new Root();
MyStatement myStatement = new MyStatement (root);
root.setStatement(myStatement);
networking.TDAConnection tdaConn = new networking.TDAConnection(root);
tdaConn.login();
root.setTDAConnection(tdaConn);
Table table = new Table(root);
new Thread(table).start();
}
public static void main(String[] args){
new StackOverflowCode();
}
}
class Table implements Runnable {
Root root;
private ArrayList <OptionRow> optionList;
public Table(Root r){
// guess initial list size is 125,000 symbols
optionList = new ArrayList<>(125000);
root = r;
}
@Override
public void run() {
getUnderlyingList();
}
public void addAll(ArrayList options) {
optionList.addAll(options);
}
private void getUnderlyingList() {
try {
final TreeSet dbStocks = (TreeSet) root.getMyStatement().getStockSymbolsFromOptionsFilterDatabase();
ExecutorService pool = Executors.newFixedThreadPool(10);
String stock = null;
for (Iterator it = dbStocks.iterator(); it.hasNext(); stock = (String) it.next()) {
pool.submit(new Download(root, stock, this));
}
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
class Download implements Runnable {
private String stock;
private Root root;
private Table parent;
public Download(Root root, String stock, Table parent) {
this.root = root;
this.stock = stock;
this.parent = parent;
}
@Override
public void run() {
PopulateTable pop = new PopulateTable(root, stock, parent);
pop.populateTable();
}
}
class PopulateTable {
private String stockSymbol;
private Root root;
private Table parent;
private ArrayList <OptionRow> optionList;
private ParseBinaryOptionChain pBOC;
private volatile OptionRow option;
private volatile GregorianCalendar expDate;
public PopulateTable(Root root, String stockSymbol, Table parent) {
this.root = root;
this.stockSymbol = stockSymbol;
this.parent = parent;
optionList = new ArrayList<>();
}
public synchronized void populateTable() {
try {
// Download all the options associated with the stock symbol
// and parse them directly from the Internet.
pBOC = root.getTDAConnection().requestBinaryOptionChain(stockSymbol);
root.getMyStatement().setLastStockPrice(stockSymbol, pBOC.getsLast());
root.getMyStatement().setCompanyName(stockSymbol, new networking.YahooConnect(root).symbolLookup(stockSymbol));
// Here's where we will be going through each option. Collect the info
// we are interested in here. Put the info in the options table.
while (pBOC.next()&&root.buttonIsOnline()){
boolean isCall = false;
if (pBOC.getPutCallIndicator()=='C')
isCall = true;
String date = pBOC.getODate();
expDate = new GregorianCalendar (
Integer.parseInt(date.substring(0, 4)),
(Integer.parseInt(date.substring(4, 6))-1),
Integer.parseInt(date.substring(6, 8)));
System.out.println ("<"+Thread.currentThread().getName()+"> ("+pBOC.getOSymbol()+") populateTable expDate = "
+root.getHelp().getSdfYearMonthDay().format(expDate.getTime())
+" year="+Integer.parseInt(date.substring(0, 4))
+" month="+Integer.parseInt(date.substring(4, 6))
+" day="+ Integer.parseInt(date.substring(6, 8)));
System.out.println (Thread.currentThread().getName()+" 2) OFT-Thread ("+pBOC.getOSymbol()+") populateTable expDate = "
+root.getHelp().getSdfYearMonthDay().format(expDate.getTime())
+" year="+Integer.parseInt(date.substring(0, 4))
+" month="+Integer.parseInt(date.substring(4, 6))
+" day="+ Integer.parseInt(date.substring(6, 8)));
option = new OptionRow(root, expDate, new GregorianCalendar());
optionList.add(option);
}
// add all the options to the parent table
parent.addAll(optionList);
// Saves all the downloaded info to the database
root.getMyStatement().insertOptionsInfoForFilter(optionList);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
class OptionRow {
private Root root;
private volatile GregorianCalendar expDate;
public OptionRow (Root root, GregorianCalendar expDate, Object... o) {
this.root = root;
this.expDate = expDate;
}
}
如果有人能告诉我为什么会发生错误以及如何解决它,我会非常喜欢它?
【问题讨论】:
-
你想做什么?可能有一种正确的方法可以实现它,因为您的代码看起来很可疑(尤其是
let other threads finish部分,您可以在其中使线程休眠。 -
我的第一个猜测是 Calender 的使用,因为它不是(类,不是它当前的使用)不是线程安全的,虽然在这里看起来没问题,你使用新实例作为我明白了。
-
downloadInfo声明在哪里? -
您从代码中删除了太多内容。至少需要
problems变量的存储位置以及产生问题中显示的输出的位置。 -
同上@Kayaman。添加 sleep() 调用永远不会修复同步错误:它只会降低它被您的测试捕获的可能性,因此当它最终发生时更有可能在现场造成伤害。
标签: java multithreading thread-safety shared-memory