使用 synchronized 会使这段代码顺序化吗?

Does using synchronized makes this code sequential?

我是线程池的新手,正在学习使用 synchronized

此代码存在竞争条件问题:

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit ;
public class Counter implements Runnable{
    int count;
    public Counter(){
        count=0;
    }
    public void run(){
        count++;
    }
    public static void main(String[] args) throws 
    InterruptedException{
        ExecutorService exec=Executors.newFixedThreadPool(2);
        Counter task=new Counter();
        for (int i=0;i<1000;i++ ) {
            exec.execute(task); 
        }
        exec.shutdown();
        exec.awaitTermination(50L,TimeUnit.SECONDS);
        System.out.println(task.count);
    }
}

在此代码中解决了竞争条件:

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit ;
public class Counter implements Runnable{
    int count;
    public Counter(){
        count=0;
    }
    public synchronized void run(){
        count++;
    }
    public static void main(String[] args) throws 
    InterruptedException{
        ExecutorService exec=Executors.newFixedThreadPool(2);
        Counter task=new Counter();
        for (int i=0;i<1000;i++ ) {
            exec.execute(task); 
        }
        exec.shutdown();
        exec.awaitTermination(50L,TimeUnit.SECONDS);
        System.out.println(task.count);
    }
}

但我认为在第二个实现中使用线程是没有意义的,因为执行将是 "sort of" 顺序的。因为两个线程中只有一个线程可以访问对象监视器,而另一个线程将等到第一个线程执行,并且只有在第一个线程完成时才会成为监视器的访问权限。这听起来像是顺序的。

如有错误请指正。非常感谢任何帮助。

使 运行() 方法同步是一种无效的多线程。它当然会导致顺序处理

请参阅Should you synchronize the run method? Why or why not?

是的。非静态方法上的 synchronized 关键字将给定方法限制为按实例顺序执行。您只有一个 Counter 实例,并且正在为所有任务重用它,因此,即使您有一个包含 2 个线程的线程池,在任何给定时间也只有一个会执行 运行()。

首先,上面提供的代码只是为了学习案例,让学习者知道在使用多线程时可能会出现竞争条件,同时如何处理竞争条件。在上面提供的代码中,由于 synchronized 关键字使得只有一个线程可以访问块(临界区),因此它确实是顺序的。考虑到上下文切换时间可以忽略不计和公平调度,多线程版本的运行时间会比单线程版本使用的时间多

但退一步说。我们可以从多线程中获得的好处是我们可以同时执行一个程序的多个部分。因此,多线程通过多任务处理可以最大限度地利用 CPU。所以实际上我们需要考虑两种情况。一个是 CPU 繁重的程序(例如,您代码中的示例)。另一个是具有大量 I/O 或网络绑定处理(在网络应用程序中很常见)的程序。

第二个会很有趣,因为整个过程不会发生在 CPU。

例如,我们有

task1:2 秒从 I/O 请求一些数据 + 2 秒 CPU

task2:2 秒从 I/O 请求一些数据 + 2 秒 CPU

如果我们运行在一个线程中,那么总时间就是2s + 2s + 2s + 2s = 8 s。 但是如果我们运行在两个线程中,我们假设线程上下文切换会花费1s(实际时间很快,我们可以忽略)

开始 -> task1 I/O 在 0

请求

-> task1 块和线程上下文更改为 task2 并且 taks2 在 1 秒时开始发出 I/O 请求

-> task2 块和线程上下文更改为 task1 并且 task1 在 2s 时从 IO 获取所有数据

-> 任务 2 在 4 秒时完成 CPU 操作

-> 线程上下文在 5 秒时更改为 task2

-> 任务 2 在 7 秒时完成 CPU。

-> 结束

考虑 1 秒上下文切换时间和 3 次上下文切换(3 秒)= 7 秒。这仍然比单线程程序好。