【问题标题】:Using threads to modify an object使用线程修改对象
【发布时间】:2018-10-16 13:07:51
【问题描述】:

我是线程新手。我想让两个线程将整数增加到某个值。因为 int 类型是不可变的,所以我切换到原子整数。我还尝试将一个 int 包装到一个类中,但这也不起作用。我也尝试了 static/volatile int ,但没有奏效。我也尝试使用公平政策。主要问题是“counterObj”没有正确递增,即使它被注入到两个线程,它仍然设置为 0。

我预期的跑步行为:

thread       value
thread 0     0
thread 1     1
thread 0     2
...

到目前为止我写的:

import java.util.concurrent.atomic.AtomicInteger;

public class Application {
    public static void main(String[] args) {
        Application app = new Application();
        try {
            app.launch();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void launch() throws InterruptedException {
        int increments = 100;
        AtomicInteger counterObj = new AtomicInteger(0);
        CounterThread th1 = new CounterThread("1", counterObj, increments);
        CounterThread th2 = new CounterThread("2", counterObj, increments);
        th1.start();
        th2.start();


        System.out.println(counterObj.get());

    }


}

import java.util.concurrent.atomic.AtomicInteger;

public class CounterThread implements Runnable {
    private final String threadID;
    private AtomicInteger counterObj;
    private int bound;

    public CounterThread(String threadID, AtomicInteger counter, int bound) {
        this.threadID = threadID;
        this.counterObj = counter;
        this.bound = bound;
    }

    @Override
    public synchronized void run() {
        while (counterObj.get() < bound) {
            synchronized (this) {
                counterObj.incrementAndGet();
            }
        }
        System.out.println("Thread " + threadID + " finished");
    }


    public void start() throws InterruptedException {
        Thread thread = new Thread(this, threadID);
        thread.join();
        thread.start();
    }
}

干杯!

【问题讨论】:

  • 仅作记录:调用您的类 counterThread 然后使其 实现 可运行,但在内部创建一个 Thread 对象本身......这是一个非常奇怪的设计。它给你的代码增加了大量的混乱,没有任何必要这样做。使类扩展线程本身,或者给它一个不同的名称,以更好地描述它正在做什么。
  • 还有start方法...我觉得线程的控制应该在main(调用join()和start)...
  • 不要同步运行,也不要在同步方法中执行 synchronize(this)。您需要在共享对象上进行同步才能使用。
  • AtomicInteger 的全部意义在于它本身强制执行原子性。您不需要需要围绕AtomicInteger.incrementAndGet() 进行自己的同步,因为该类保证它自己会以原子方式进行。

标签: java java-threads


【解决方案1】:

我认为您的程序在您的线程有机会做任何事情之前退出(可能是由于您的启动和连接的顺序。我会将您的线程启动逻辑移动到您的主(或启动)方法中。类似于以下内容.

Thread thread1 = new Thread(new MyCounterRunnable("1", counterObj, increments));
Thread thread2 = new Thread(new MyCounterRunnable("2", counterObj, increments));

然后,在您的主线程中,您需要在启动线程之后调用 join ...如下:

thread1.start(); // starts first thread.
thread2.start(); // starts second thread.

thread1.join(); // don't let main exit until thread 1 is done.
thread2.join(); // don't let main exit until thread 2 is done.

【讨论】:

  • 我会做的其他更改。 1. 在 counterObj 上同步(而不是“this”)。 2.从'public void run'方法中移除同步修饰符。
  • 这些好建议 - 将它们添加到答案中,而不是让它们迷失在评论中。顺便说一句,不需要同步块 - AtomicInteger 负责同步。
  • 这非常有效。我删除了修饰符并将开始和加入逻辑移动到 main.js 中。非常感谢!
【解决方案2】:

您真正想要的是一次只有一个线程增加一个 int。 int 变量是您想要在同步块中的资源,因此不同的线程可以一次增加一个。 这可以单独使用 syncrhonize 来完成。 免责声明:我没有运行代码,因此它可能有一些拼写错误或要从 Application 类中删除的异常。

public class Application {

  private int theVar = 0;
  private int increments = 100;

  public static void main(String[] args) {
    Application app = new Application();
    try {
        app.launch();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
  }

  public synchronized addOne(){
    this.theVar++;
  }

  private void launch() throws InterruptedException {
    Runnable counter1 = new Counter(this, increments), counter2 = new Counter(this, increments);
    Thread t1 = new Thread(counter1);
    Thread t2 = new Thread(counter2);
    t1.start();
    t2.start();
  }
}

一个计数器类

public class Counter implements Runnable{
  private Application app;
  int rounds = -1;

  public Counter(Application app, rounds){
    this.app = app;
    this.rounds = rounds;
  }
  public void run(){
    while(int i=0; i<rounds; i++){
        this.app.addOne();
    }
  }
}

【讨论】:

    【解决方案3】:

    AtomicInteger 负责原子性本身,因此您不需要使用 synchronized - 但前提是您遵守规则,并在一次调用中执行原子操作。

    你没有这样做,因为你调用counterObj.get() 然后取决于结果counterObj.incrementAndGet()。您需要避免这种情况,因为您希望检查和更新成为同一个原子工作块的一部分。

    你可以接近:

    while(counterObj.incrementAndGet() < bound) {} ;
    

    但这总是至少增加一次,这一次可能太多了。

    稍微多一些:

    IntUnaryOperator incrementWithLimit = x -> 
       ( x < bound ? x + 1 : x );
    
    while(counterObj.updateAndGet(incrementWithLimit) < bound) {};
    

    也就是说,我们创建了一个函数,该函数仅在小于 bound 时才递增一个数字,并且我们告诉 AtomicInteger 应用它。

    【讨论】:

      【解决方案4】:

      您的代码有几个问题:

      Thread.join 方法仅在线程已启动时才有效,否则它什么也不做。所以你必须重新排序你的代码,但是如果你只是在 start 之后移动 join 方法,当通过调用 CounterThread.start 启动第一个线程时,主线程会一直等到启动的线程完成,在 Thread.join 方法中阻塞,然后才会继续启动第二个线程。一种解决方案是在 CounterThread 类中创建一个附加方法,该方法将在两个线程都启动后调用:

      public void waitFinish() throws InterruptedException {
          thread.join();
      }
      

      synchronized (this) 正在同步您调用 new CounterThread(...) 时创建的 CounterThread 实例,但是您有两个实例,因此每个实例都将在不同的对象上同步。为了使同步工作,您需要使用对象的公共实例,在这种情况下,您可以使用共享的 counterObj

      只有 AtomicInteger 方法保证是线程安全的,所以在你检查是否已经在同步块之外到达边界后,当进入同步块时,该值已经可以被另一个线程更改.因此,您需要在同步块内进行重新检查,或者在检查和增量之前首先同步共享锁(counterObj)。

              while (true) {
                  synchronized (counterObj) {
                      if (counterObj.get() < bound)
                          counterObj.incrementAndGet();
                      else break;
                  }
              }
      

      请注意,AtomicInteger 类的同步方法现在没有帮助,但因为它是一个可变对象,所以将其用作共享锁会有所帮助。如果您使用 Integer 代替,它是不可变的,当您递增它时将创建一个新实例。所以现在,它唯一的功能是一个保存整数结果的包装器。

      把它们放在一起:

      public class Application {
          public static void main(String[] args) {
              Application app = new Application();
              try {
                  app.launch();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      
          private void launch() throws InterruptedException {
              int increments = 100;
              AtomicInteger counterObj = new AtomicInteger(0);
              CounterThread th1 = new CounterThread("1", counterObj, increments);
              CounterThread th2 = new CounterThread("2", counterObj, increments);
              th1.start();
              th2.start();
              th1.waitFinish();
              th2.waitFinish();
      
              System.out.println(counterObj.get());
          }
      }
      
      public class CounterThread implements Runnable {
          private final String threadID;
          private AtomicInteger counterObj;
          private int bound;
          private Thread thread;
      
          public CounterThread(String threadID, AtomicInteger counter, int bound) {
              this.threadID = threadID;
              this.counterObj = counter;
              this.bound = bound;
          }
      
          @Override
          public void run() {
              while (true) {
                  synchronized (counterObj) {
                      if (counterObj.get() < bound)
                          counterObj.incrementAndGet();
                      else break;
                  }
              }
              System.out.println("Thread " + threadID + " finished");
          }
      
      
          public void start() throws InterruptedException {
              thread = new Thread(this, threadID);
              thread.start();
          }
      
          public void waitFinish() throws InterruptedException {
              thread.join();
          }
      }
      

      【讨论】:

        【解决方案5】:

        我已经对 AtomicInteger 进行了双重检查,这似乎是您一直在尝试完成的。

        import java.util.concurrent.atomic.AtomicInteger;
        
        public class DualCounters{
        
            public static void main(String[] args) throws Exception{
                AtomicInteger i = new AtomicInteger(0);
                int bounds = 3;
        
                Thread a = new Thread(()->{
                    int last = 0;
                    while(i.get()<bounds){
                        synchronized(i){
                            if(i.get()<bounds){
                                last = i.getAndIncrement();
                            }
                        }
                    }
                    System.out.println("a last " + last);
                });
        
                Thread b = new Thread(()->{
                    int last = 0;
                    while(i.get()<bounds){
                        synchronized(i){
                            if(i.get()<bounds){
                                last = i.getAndIncrement();
                            }
                        }
                    }
                    System.out.println("b last " + last);
                });
        
                a.start();
                b.start();
        
                a.join();
                b.join();
        
                System.out.println(i.get() + " afterwards");
        
            }
        }
        

        双重检查在 java 中是一个错误的概念,AtomicInteger 提供了无需任何同步即可完成此操作的工具。

        int a;
        while((a = i.getAndIncrement())<bounds){
            ...
        }
        

        现在 a 永远不会大于 while 循环内的边界。当循环结束时,ia 的值可能大于界限。

        如果这是一个问题,总是有其他方法getAndUpdate

        while((a = i.getAndUpdate(i->i<bounds?i+1:i)<bounds){
            ...
        } 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-11-07
          相关资源
          最近更新 更多