【问题标题】:Multi-threading -- a faster way?多线程——更快的方法?
【发布时间】:2014-08-08 08:36:32
【问题描述】:

我有一个类,在某个字段上有一个 getter getInt() 和一个 setter setInt(),比如字段

Integer Int; 

一个类的对象,比如SomeClass

这里的setInt() 是同步的——getInt() 不是。

我正在从多个线程中更新Int 的值。 每个线程都在获取值Int,并进行适当的设置。 线程不以任何方式共享任何其他资源。

每个线程中执行的代码如下。

public void update(SomeClass c) {

        while (<condition-1>) // the conditions here and the calculation of 
                                   // k below dont have anything to do 
                                   // with the members of c
            if (<condition-2>) {
                // calculate k here 
                synchronized (c) {                        
                        c.setInt(c.getInt()+k); 
                    //    System.out.println("in "+this.toString());
                }
            }  
}   

run() 方法只是在通过传递给它的参数从构造函数中更新的成员上调用上述方法:

public void run() { update(c); } 

当我在大序列上运行它时,线程并没有太多交错——我看到一个线程执行了很长时间,而没有任何其他线程在其间运行。

必须有更好的方法来做到这一点。

我无法更改SomeClass 或调用线程的类的内部结构。

如何才能做得更好?

TIA。

//======================================

编辑:

我不是在操纵线程的执行顺序。它们都有相同的优先级。只是我在结果中看到的表明线程没有均匀地共享执行时间——其中一个一旦接管,就会继续执行。但是,我不明白为什么这段代码应该这样做。

【问题讨论】:

  • 您能否展示一下您的 public void run() 方法的代码?如果您粘贴与线程相关的整个代码,例如如何创建多个线程以及如何启动它们,那就更好了。我怀疑您的代码中是否真的实现了多线程。
  • 请查看更新后的代码
  • 你在同一个类中有 update() 和 run() 方法吗?
  • 是的,他们在同一个班级
  • 您想知道如何克服其他线程可能出现的饥饿问题吗?如果是这样,我建议在问题的某个地方添加饥饿,因为这是一个已知问题。

标签: java multithreading time


【解决方案1】:

我在结果中看到的只是表明线程没有均匀地共享执行时间

好吧,如果您追求效率,这正是您不想要的。取消一个线程被执行并调度另一个线程通常是非常昂贵的。因此,执行one of them, once takes over, executing on 实际上是有利的。当然,如果过度使用,您可能会看到更高的吞吐量但更长的响应时间。理论上。在实践中,JVM 线程调度针对几乎所有目的都进行了很好的调整,并且您不想在几乎所有情况下都尝试更改它。根据经验,如果您对毫秒级的响应时间感兴趣,您可能希望远离它。

tl;dr:这并不是效率低下,您可能希望保持原样。

编辑:
话虽如此,使用AtomicInteger 可能有助于提高性能,并且在我看来比使用锁(synchronized 关键字)更不容易出错。但是,您需要非常努力地打击该变量才能获得可衡量的收益。

【讨论】:

  • 这是有道理的。有没有可以确定这一点的参考资料?我想知道 syn'd 块中的同步和异步方法是否会成为问题。以及其他一些类型的锁是否可以做得更好。
  • 我会参考这本书Java Concurrency in Practice。这是有关此主题的最佳资源,它讨论了您的问题。至于同步/锁定,从 JDK6 开始没有区别。如果你在文学上增加一个整数,你必须用荒谬的负载来打击它,以便基本的并发设施成为一个问题。如果我是你,我真的会在别处寻找性能改进。这本书也谈到了这一点。
【解决方案2】:

JDK 为多线程 int 访问提供了一个很好的解决方案,AtomicInteger:

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger.html

【讨论】:

    【解决方案3】:

    作为Enno Shioji has pointed out,让一个线程继续执行可能是在某些情况下执行代码的最有效方式。

    这取决于线程同步相对于您代码的其他工作(我们不知道)的成本。如果你有这样的循环:

    while (<condition-1>)
        if (<condition-2>) {
            // calculate k here 
            synchronized (c) {                        
                c.setInt(c.getInt()+k); 
            }
        }
    

    条件 1 和条件 2 的测试以及 k 的计算与同步成本相比相当便宜,热点优化器可能会决定通过将代码转换为以下内容来减少开销:

    synchronized (c) {                        
        while (<condition-1>)
            if (<condition-2>) {
                // calculate k here 
                c.setInt(c.getInt()+k); 
            }
    }
    

    (或者通过执行循环展开并在多次迭代中跨越 synchronized 块来实现更复杂的结构)。最重要的是,优化后的代码可能会更长时间地阻塞其他线程,但让拥有锁的线程更快地完成,从而整体上更快地执行。

    这并不意味着单线程执行是处理您的问题的最快方法。这也不意味着在这里使用AtomicInteger 是解决问题的最佳选择。它会产生更高的 CPU 负载,并且可能会产生很小的加速,但它并不能解决您真正的错误:

    完全没有必要在循环内高频更新c。毕竟,您的线程不依赖于及时查看c 的更新。甚至看起来他们根本没有使用它。所以正确的解决方法是将更新移出循环:

    int kTotal=0;
    while (<condition-1>)
        if (<condition-2>) {
            // calculate k here
            kTotal += k;
        }
    synchronized (c) {                        
        c.setInt(c.getInt()+kTotal); 
    }
    

    现在,所有线程都可以并行运行(假设您未在此处发布的代码不包含线程间依赖关系)并且synchronization 成本降至最低。您仍然可以将其更改为 AtomicInteger,但这不再那么重要了。

    【讨论】:

      【解决方案4】:

      回答这个问题

      我看到一个线程执行了很长时间,而没有任何其他线程在其间运行。 必须有更好的方法来做到这一点。

      您无法控制线程的执行方式。 JVM 为你做这件事,并且不希望你干涉它的工作。

      您仍然可以将yield 视为您的选项,但这也不能确保不会再次选择相同的线程。

      java.lang.Thread.yield() 方法使当前正在执行的线程对象暂时暂停并允许其他线程执行。

      【讨论】:

      • 不,不要使用yield,除非您正在处理以微秒为单位计算响应时间的代码...
      【解决方案5】:

      我发现使用 wait() 和 notify() 比使用 yield 更好。看看这个例子(从书中看到)-

      class Q {
      int n;
      boolean valueSet = false;
      synchronized int get() {
      if(!valueSet)
      wait(); //handle InterruptedException
      //
      valueSet = false;
      notify();//if thread waiting in put, now notified
      }
      
      synchronized void put(int n) {
      if(valueSet)
      wait(); //handle InterruptedException
      //
      valueSet = true;
      //if thread in get waiting then that is resumed now
      notify();
      }
      }
      

      或者你可以尝试使用 sleep() 并在 main() 中加入线程,但这不是万无一失的方法

      【讨论】:

        【解决方案6】:

        您的代码中有public void update(SomeClass c) 方法,此方法是instance method,您将在其中将对象作为参数传递。

        synchronized(c) 在您的代码中什么也不做。让我给你举个例子,

        因此,如果您将创建此类的不同对象,然后尝试使它们成为不同的线程,

           class A extends Thread{
                  public void update(SomeClass c){}
                  public void run(){
                       update(c)
                  }
                  public static void main(String args[]){
        
                       A t1 = new A();
                       A t2 = new A();
        
                       t1.start();
                       t2.start();
        
                  }
           }
        

        然后这两个 t1 和 t2 都将拥有自己的更新方法副本,并且您正在同步的引用变量 c 对于两个线程也将不同。 t1 调用它自己的 update() 方法,t2 调用它自己的 update() 方法。所以同步不起作用。

        当两个线程都有共同点时,同步将起作用。

        类似的,

           class A extends Thread{
        
                  static SomeClass c;
        
                  public void update(){
                        synchronized(c){
        
                        }
                  }
                  public void run(){
                       update(c)
                  }
                  public static void main(String args[]){
        
                       A t1 = new A();
                       A t2 = new A();
        
                       t1.start();
                       t2.start();
        
                  }
           }
        

        这样才能应用实际的同步概念。

        【讨论】:

        • 我看不出这对任何事情有什么帮助。
        • 您的代码中没有实现同步。
        • 为什么说Roam的同步块不起作用?它在块内修改的同一个对象 c 上同步。这绝对会阻止两个线程进入 same Object 的同一个块,c。当然,两个线程可以为两个不同的对象进入块,但这没关系,因为它们正在修改两个不同的对象,在这种情况下没有竞争条件。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多