无锁:将化合物,非原子操作分解为更简单的原子操作

时间:2013-03-14 21:02:54

标签: java multithreading lock-free atomicity

我正在编写金融市场数据分销商应用程序作为我的M.S项目的一部分。该应用程序即将完成,但应用程序扩展性不佳。

这是它的工作原理,我订阅了一个“假”交换来获取市场数据。 订阅后,我得到一个初始快照,然后我不断收到增量...

1) Subscribe for IBM.
2) Snapshot : Buy:50|Size:1400||Sell:49|Size:1000
(At buy price of 50, 1400 shares and at sell price of 49, 1000 shares available)
3) Update1: -1|10||-2|25
(Buy price is now 49 (50-1), buy Size is 1410. Sell price is 47 and sell size is 1025)
4) Update2 ...
.
.
.

我有一个处理市场数据的类,下面的dataUpdate()是一个回调方法,“假”交换应用程序在一个线程中调用。

class MarketDataProcessor:
Map<String, MarketData> marketDataMap      = new HashMap<String, MarketData>(1000);
ConcurrentMap<String, MarketData> distMap  = new ConcurrentHashMap<String, MarketData>();

//**Always called by one thread (MarketDataThread)**
    public void dataUpdate( MarketDataUpdate dataUpdate ){

            try{
                 //IBM
                 String symbol   = dataUpdate .getSymbol();             
                 MarketData data = marketDataMap.get( symbol );

                 if ( data = null ){
                    data = new MarketData( dataUpdate );
                 }else{
                    //Apply the delta here and save it ...
                    marketDataMap.put( symbol, data );
                 }

                 distMap.put( symbol, data );

            }catch( Exception e ){
               LOGGER.warn("Exception while processing market data.");
               LOGGER.warn("Exception: {}", e);
            }
    }

从交换机获取市场数据后,我需要以线程安全的方式分发它。 这种方法不能很好地扩展,因为它可以被20多个线程调用,它使用外部锁来确保原子性。

public final double[] getData( String symbol, Side side ){
     double[] dataArray = {0.0, 0.0};

     synchronized( LOCK ){
        MarketData data = distMap.get( symbol );
        dataArray       = ( side == BUY ) ? getBuyData(data) : getSellData(data); 
     }

  return dataArray;
}

我建议的解决办法是将上述方法分成两部分。

//No external lock as it uses a ConcurrentHashMap
public final MarketData getData( String symbol ){
       return distMap.get( symbol );
}

//State of this method is now confimed to the
//stack of the calling thread, therefore thread safe.
public final double[] getData( MarketData data, Side side ){
       return ( side == BUY ) ? getBuyData(data) : getSellData(data); 
}

承认这将改变api并让用户调用两个方法而不是一个方法,如果不使用外部锁,它是否会使其成为线程安全的?

谢谢。

3 个答案:

答案 0 :(得分:2)

将许多线程转储到一个需要时间的同步线程中并不是一个好主意。

您可以将这些结果转储到同步队列中以便稍后进行分析吗?当许多线程将项目放入队列时,单个线程可以将项目拉出队列。

答案 1 :(得分:1)

首先,我不知道你的LOCK控制访问的资源是什么。您的代码的哪一部分是不安全的,为什么您认为使用LOCK有帮助?您的新方法也没有提供与原始方法相同的签名。你希望人们像

一样使用它吗?
getData(getData(symbol), side)

如果没有锁定的原始代码不是线程安全的,那么你的新方式与此没什么不同。

回到原始实现:您在受LOCK保护的代码段中访问的唯一资源是distMap。没有其他地方在使用LOCK。因此,您在此处所做的是,您只需要1个线程即可从distMap获取 获取 数据。但是我没有看到这样的理由:第一个distMap是一个ConcurrentMap,多线程访问它是线程安全的。其次,对于每种类型的Map,简单地获取数据始终是线程安全的,因为没有人正在改变状态。通常,执行同步控制所需要的应该包括状态更改逻辑,这是将数据放入映射的位置(尽管在这段特定的代码中,没有理由添加额外的同步控制),但是你没有这样做。我根本无法理解你的LOCK的全部目的。

对您的一些评论:只是盲目地使用synchronized / LOCK等不会自动将代码更改为线程安全。您需要知道什么是非线程安全的,并相应地进行同步控制。另一个评论是:将非原子方法分解为原子块不会使您的代码线程安全。试想一下:Java中几乎所有最低级别的操作都是原子的。这是否意味着您的代码是自动线程安全的,因为您正在调用一堆原子操作?显然不是。

答案 2 :(得分:0)

虽然其他答案提供了很好的建议,但还有另一个可用锁定选项:使用持久性地图(例如来自pcollections)。读者线程总是会有一致的数据快照;编写器线程将创建创建新版本的地图 - 但不复制所有数据。