【问题标题】:Problem with thread synchronizing in JavaJava中的线程同步问题
【发布时间】:2019-06-06 16:12:56
【问题描述】:

我很清楚这可能被认为是重复的,但是考虑到我的问题,我在这里遇到了很多答案,我无法提出解决方案。

我将我的runnable与一个由多个线程共享的对象同步,并显式同步了我在里面使用的方法,但程序的结果总是3000。

我尝试锁定 Counter 类,但它不会改变任何事情。 谁能解释一下为什么我的操作在这个特定示例中不起作用?

    public static void zad3() {
        var counter = new Counter();

        var toRun = new Runnable() {
            @Override
            public void run() {
                synchronized (counter) {
                    for (var i = 0; i < 1000; i++) {
                        counter.add(1);
                    }
                }
            }
        };

        var t1 = new Thread(toRun);
        var t2 = new Thread(toRun);
        var t3 = new Thread(toRun);

        t1.start();
        t2.start();
        t3.start();

        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("counter = " + counter.getCount());
   }
public class Counter {
    protected long count_ = 0;

    public synchronized void add(long value) {
        count_ += value;
    }

    public long getCount() {
        return count_;
    }
}

编辑: 正如建议的那样,问题在于每个线程不断运行1000 次。 我的解决方案:

        var toRun = new Runnable() {
            @Override
            public void run() {
                synchronized (counter) {
                    for (var i = counter.getCount(); i < 1000; i++) {
                        counter.add(1);
                    }
                }
            }
        };

【问题讨论】:

  • 您期待什么结果?
  • @Jeppe 结果总是3000,而我正在努力实现1000
  • 那么你应该限制循环在counter.getCount() &lt; 1000而不是i时运行。这意味着 2 个操作必须是原子的(读取,然后写入),(如果您删除循环周围的同步)。
  • 如果你有 3 个线程,每个线程递增 1000 次,为什么你期望结果为 1000?
  • @Jeppe 是的,这很有意义。谢谢!

标签: java multithreading runnable executorservice


【解决方案1】:

好吧,您已经围绕“counter”变量同步了完整的 for 循环,这意味着每个线程将运行一次该块。 3 X 1000 = 3000

这个块将每个线程执行一次

 for (var i = 0; i < 1000; i++) {
                        counter.add(1);
 }

更新:从您的 cmets 判断,您希望中​​断 1000 个示例代码可以是:

 t1.start();
 t2.start();
 t3.start();

while(counter.getValue()<1000) {
    Thread.sleep(20)
}

另一个建议:

public class Incremetor extends Runnable {
   Counter counter;

public Incremetor(Counter counter) {
    this.counter = counter;
}
public void run() {
   counter.increment();
}

}

ExecutorService executorService = Executors.newFixedThreadPool(8); // this mean 8 threads in total to do your runnables.
for (int i=0;i<1000;++i) {
     executorService.submit(new Incrementor(counter));        
}

【讨论】:

  • 我明白了,但是有没有办法可以拆分 1000 我尝试在线程之间获取?因为这是我的练习,所以我需要使用 for 循环来完成。
  • @bart-kosmala 我不明白你想说什么。解释更多。
  • 您已回答我的问题,我将其标记为已接受。我的修复将在编辑后的问题中。
  • 没有最好的方法。有很多方法,都取决于情况。在您的情况下,最好的方法是根本不使用线程:) 或者只选择一个多线程选项并解释它解决了什么问题。
  • 在我的机器上,使用N = 1_000_000_000L 的顺序解决方案采用0m1.181s,而带有线程本地计数器的8 线程解决方案采用0m0.208s。这取决于问题,可能并不总是可以在本地工作线程。一般来说,您应该考虑当 N 增加无穷大时解决方案的表现如何。
【解决方案2】:

所以问题是你让每个线程尝试 1000 次增量,所以你需要这样的东西:

while (counter.getCount() < 1000) {
     counter.add(1);
}

您提供的解决方案可能会给您正确的结果,但实际上您只是从 1 个线程递增计数器。当您使用synchronized(object) { } 创建一个同步块时,所有线程都将尝试获取该块的锁,但只有一个线程会。这意味着在您的解决方案中,第一个获得锁的线程将执行所有 1000 个增量。当线程释放锁并让其他线程获得它时,工作已经完成。因此,在 3 个线程之间实际分配增量的解决方案不应同步整个 for 循环。

如果你运行我建议的 while 循环,你会得到更接近 1000,但实际上可能超过 1000。记住运行你的程序 10 次或设置一个运行它 100 次的测试函数并报告回来。问题在于,从阅读counter.getCount() 的角度来看,该值可能已经被另一个线程更改。要可靠地始终获得 1000,您可以确保对计数器的读取和写入的专有权利:

while (true) {
    synchronized (counter) {
        if (counter.getCount() < 1000) {
            counter.add(1);
        } else {
            break;
        }
    }
}

请注意,像这样递增 一个 变量,很慢。你只做 1000 个,但尝试用 10 亿个。事实上,3 线程版本(在我的 PC 上)需要 1 分 17 秒,而简单的顺序循环需要约 1.2 秒。您可以通过在线程之间分配工作负载并让它们在具有独占权限的本地计数器上工作,然后最后添加结果来解决此问题。

【讨论】:

  • 好点,在进一步处理线程时我会牢记这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
  • 1970-01-01
  • 2020-02-01
  • 1970-01-01
相关资源
最近更新 更多