为什么多线程似乎没有加速我的Web应用程序?

时间:2009-05-18 14:26:45

标签: java performance multithreading

class ApplicationContext{
    private final NetworkObject networkObject = new networkObject();

    public ApplicationContext(){
      networkObject.setHost("host");
      networkObject.setParams("param");
    }

    public searchObjects(ObjectType objType){
        networkObject.doSearch(buildQuery(objType));
    }
}

class NetworkObject{
    private final SearchObject searchObject = new SearchObject();

    public doSearch(SearchQuery searchQuery){
        searchObject.search(searchQuery); //threadsafe, takes 15(s) to return
    }
}

考虑一个运行Web应用程序的Web服务器,它只创建一个ApplicationContext实例(singleton)并使用相同的applicationInstance来调用searchObjects,例如。

 ApplicationContext appInstance = 
                  ApplicationContextFactory.Instance(); //singleton

每个对网页的新请求都说'search.jsp'会拨打电话

 appInstance.searchObjects(objectType);

我向'search.jsp'页面发出了1000个请求。所有线程都使用相同的ApplicationContext实例,而searchObject.search()方法需要15秒才能返回。我的问题是当所有其他线程执行searchObject.search()函数或所有线程同时执行searchObject.search()时,所有其他线程等待轮到他们执行(15秒),为什么?

我希望我的问题非常明确吗?

更新 谢谢大家澄清我的怀疑。这是我的第二个问题,当我这样做时应该观察到性能的差异:

public synchronized doSearch(SearchQuery searchQuery){
    searchObject.search(searchQuery); //threadsafe, takes 15(s) to return
}

public doSearch(SearchQuery searchQuery){
    searchObject.search(searchQuery); //threadsafe, takes 15(s) to return
}

我认为使用没有synchronized关键字的'doSearch'功能应该可以提供更高的性能。但是,当我今天测试它时,结果却出现了另一种情况。当我使用synchronized关键字时,性能相似或有时更好。

任何人都可以解释这种行为。我应该如何调试这些案件。

此致

佩里

7 个答案:

答案 0 :(得分:5)

嗯,你没有指定代码中的任何同步,所以没有任何其他证据我怀疑所有线程将同时运行。如果SearchObject.search包含某些同步,那么这显然会限制并发性。

请注意,您的JSP容器可能正在使用线程池来处理1000个请求,而不是创建1000个线程。

编辑:至于为什么synchronized可能更快:有时并发对吞吐量实际上没有帮助。上下文切换,磁盘瓶颈,缓存未命中等等都会产生这种影响。通常通常拥有比核心更多的运行线程并不是一个好主意。

对于一个真实的例子,假设你有一千个购物者都想从一个相当小的商店买东西。你会怎么做?将所有1000个同时放入商店,或者在任何时间将其保持在商店中相当小的数量,并在外面排队?

答案 1 :(得分:1)

明智的做法是认识到绩效是针对特定环境的。在这种情况下,它可能是您的笔记本电脑或测试服务器上的软件性能。在考虑优化代码之前检查类似于生产环境的性能是明智的,因为那里的瓶颈可能与开发机器上的瓶颈完全不同。

作为一个例子;当我在笔记本电脑上使用大型数据库测试我的软件时,我总是被硬盘IO绑定。但是在生产中,数据库服务器有足够的内存和快速的磁盘,因此优化我的IO软件是不明智的。

类似于线程;我的笔记本电脑中的处理器可以同时运行一个或两个进程。拥有8个线程并不会加快速度。然而,生产机器可以同时处理8个螺纹。

我认为比性能更重要的是语义。使用像synchronous这样的关键字不仅对编译器有用,而且对(下一个)开发人员也有帮助。

通过使用同步,您可以与ApplicationContext上的所有其他同步方法共享锁,也可以使用与searchObject无关的方法。 就个人而言,我非常怀疑你希望在一个名为ApplicationContext的对象上进行同步。

如果searchObject不是线程安全的,我可能会推荐一个锁定对象。这涉及到口味:

public void doSearch(SearchQuery searchQuery){
   synchronized(searchObject) {// Only if searchObject is guaranteed to be null
       searchObject.search(searchQuery); //threadsafe, takes 15(s) to return
  }
}

public class ApplicationContext {
    private SearchObject searchObject = null;
    private final Object searchObjectLock = new Object();    

    public void doSearch(SearchQuery searchQuery){
       synchronized(searchObjectLock) {
           searchObject.search(searchQuery); //threadsafe, takes 15(s) to return
      }
    }
}

不要忘记锁定每次使用searchObject以防止线程故障。使用这种细粒度锁定机制,您至少可以将ApplicationContext保留给不需要与searchObject相关的功能的类。

在您的情况下,我不会使用任何同步,因为它不是必需的,并在确定瓶颈之前检查类似生产的硬件。

如果searchObject使用数据库,请确保数据库属性已编入索引并使用该索引。如果需要进行1000次全表扫描,无论如何都不会很快......

答案 2 :(得分:0)

在您的情况下,它们将同时执行。

如果您想阻止这种情况,则需要进行某种同步以防止这种情况发生(例如,将方法声明为同步或使用锁定)。

ETA:

如果将doSearch()方法声明为synchronized,则一次只能有一个线程调用它。其他线程将阻塞,直到第一个线程完成,等待的线程将一次“放入”一个。你可以想象,如果你有很多线程调用那个函数,这将会破坏你的性能。

答案 3 :(得分:0)

如果你没有同步,那么每个线程将同时运行而不是阻塞锁。

请注意

// threadsafe

(如评论所示)意味着它可以正常工作,多个线程访问它 - 它会阻止线程。

答案 4 :(得分:0)

除非你的班级是单身IIRC,否则他们都可以同时执行它,除非它被宣布为同步。

答案 5 :(得分:0)

如果SearchObject.search已同步,则为yes。否则,试试看吧。

答案 6 :(得分:0)

为什么需要15秒?如果它正在等待磁盘访问并且您只有一个磁盘,那么无论您拥有多少线程,您都受到磁盘搜索速度的限制。在这种情况下,更多线程可能会更慢。