Java 中的同步

Java 中的同步是什么?

在 Java 中,同步是指控制多个线程访问任何共享资源的能力。当我们需要只允许一个线程访问共享资源时,这是一个理想的选择。

这种执行方法通常被称为“异步”编程。这些处理器上也有线程,线程是轻量级的进程,可以同时执行指令。

同步的类型

Java 中有两种同步方法

1) 进程同步

2) 线程同步。

让我们详细研究线程同步和进程同步

进程同步:它管理程序之间的同步。例如,“Microsoft Word”和“Acrobat reader”等程序作为独立进程运行。

线程同步:两个或多个线程并发执行临界资源称为线程同步。您可以进一步将其分为互斥和线程间通信。

Java 中的锁是什么?

Java 中的锁是围绕一个称为监视器或锁的内部实体构建的。所有对象都有一个与之关联的锁。因此,需要对对象字段进行一致访问的线程必须先获取对象的锁才能访问它们,并在完成工作后释放该锁。这确保一次只有一个线程访问共享数据。

带有同步的多线程程序

多线程程序是受保护的、不受共享相同资源的其他线程干扰的方法或代码块,使用 `synchronized` 关键字指示。

使用同步方法

任何声明为同步的方法都称为同步方法。它也用于锁定对象以访问任何共享资源。因此,当一个线程调用同步方法时,它会自动获取该对象的锁,并在完成任务后释放它。

注意: synchronized 关键字不能与类和变量一起使用。关键字只能与方法和代码块一起使用。

为什么要使用同步方法?

  • 它用于锁定对象以访问任何共享资源。
  • 当调用同步方法时,对象会获取锁。
  • 在线程完成其功能之前,锁不会被释放

语法

Acess_modifiers synchronized return_type method_name (Method_Parameters) {
}
class MathService {
    synchronized void getSumOfArray(int[] numbers) {
     int sum = 0;

         for (int number : numbers) {
             System.out.println(Thread.currentThread()
                     .getName()
                     + " adds "
                     + sum + " to "
                     + number + " to get -> "
                     + (sum += number));

             try {
                 Thread.sleep(500);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
    }
}
public class Synchronization {
    public static void main(String[] args) {
        MathService mathService = new MathService();

        Thread threadOne = new Thread(() ->
                mathService.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadTwo = new Thread(() ->
                mathService.getSumOfArray(new int[]{20, 21, 22}));

        threadOne.start();
        threadTwo.start();
    }
}

代码解释

运行此示例并观察线程 `0` 首先获取 `mathService` 对象的锁,并独占使用此锁直到它完成执行。线程 `0` 和 `1` 在此代码中没有交错。输出如下所示。

输出

Thread-0 adds 0 to 10 to get -> 10
Thread-0 adds 10 to 11 to get -> 21
Thread-0 adds 21 to 12 to get -> 33
Thread-1 adds 0 to 20 to get -> 20
Thread-1 adds 20 to 21 to get -> 41
Thread-1 adds 41 to 22 to get -> 63

使用同步代码块

假设您不想同步整个方法。相反,您只想同步几行代码。此时,同步代码块有助于同步选定的 Java 代码。同步方法的锁在方法上访问,而同步代码块的锁在对象上访问。

class MathService {
    void getSumOfArray(int[] numbers) {
        synchronized (this){
            int sum = 0;

            for (int number : numbers) {
                System.out.println(Thread.currentThread()
                        .getName()
                        + " adds "
                        + sum + " to "
                        + number + " to get -> "
                        + (sum += number));

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
public class Synchronization {
    public static void main(String[] args) {
        MathService mathService = new MathService();

        Thread threadOne = new Thread(() ->
                mathService.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadTwo = new Thread(() ->
                mathService.getSumOfArray(new int[]{20, 21, 22}));

        threadOne.start();
        threadTwo.start();
    }
}

代码解释

运行此代码后,您会注意到它运行正常,没有受到干扰。在同步方法中,锁由方法应用,但在同步代码块中,锁由对象应用。请确保输出如下所示。

输出

Thread-0 adds 0 to 10 to get -> 10
Thread-0 adds 10 to 11 to get -> 21
Thread-0 adds 21 to 12 to get -> 33
Thread-1 adds 0 to 20 to get -> 20
Thread-1 adds 20 to 21 to get -> 41
Thread-1 adds 41 to 22 to get -> 63

代码解释

运行此代码后,您会注意到它运行正常,没有受到干扰,这正是我们所期望的。在同步方法中,锁由方法应用,但在同步代码块方法中,锁由对象应用。

使用静态同步

在 Java 同步中,如果存在多个对象,两个线程可能会获取锁并进入同步代码块或代码块,每个对象有独立的锁。为了避免这种情况,可以使用静态同步。`synchronized` 关键字将用于静态方法之前。

注意:在静态同步中,锁访问的是类,而不是对象和方法。

演示多个对象锁定问题的代码

class MathService {
    synchronized void getSumOfArray(int[] numbers) {
            int sum = 0;

            for (int number : numbers) {
                System.out.println(Thread.currentThread()
                        .getName()
                        + " adds "
                        + sum + " to "
                        + number + " to get -> "
                        + (sum += number));

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
    }
}
public class Synchronization {
    public static void main(String[] args) {
        MathService mathService = new MathService();
        MathService mathService1 = new MathService();

        Thread threadOne = new Thread(() ->
                mathService.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadTwo = new Thread(() ->
                mathService.getSumOfArray(new int[]{20, 21, 22}));
        Thread threadThree = new Thread(() ->
                mathService1.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadFour = new Thread(() ->
                mathService1.getSumOfArray(new int[]{20, 21, 22}));

        threadOne.start();
        threadTwo.start();
        threadThree.start();
        threadFour.start();
    }
}

代码解释

当我们创建 `MathService` 的另一个实例时,我们会引入线程干扰,因为它们将与两个对象交错。请注意,线程 `0` 和线程 `2` 与两个对象交错,而线程 `1` 和 `3` 与两个对象交错。

输出

Thread-0 adds 0 to 10 to get -> 10
Thread-2 adds 0 to 10 to get -> 10
Thread-0 adds 10 to 11 to get -> 21
Thread-2 adds 10 to 11 to get -> 21
Thread-0 adds 21 to 12 to get -> 33
Thread-2 adds 21 to 12 to get -> 33
Thread-1 adds 0 to 20 to get -> 20
Thread-3 adds 0 to 20 to get -> 20
Thread-1 adds 20 to 21 to get -> 41
Thread-3 adds 20 to 21 to get -> 41
Thread-1 adds 41 to 22 to get -> 63
Thread-3 adds 41 to 22 to get -> 63

使用同步静态方法的相同代码

class MathService {
    synchronized static void getSumOfArray(int[] numbers) {
            int sum = 0;

            for (int number : numbers) {
                System.out.println(Thread.currentThread()
                        .getName()
                        + " adds "
                        + sum + " to "
                        + number + " to get -> "
                        + (sum += number));

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
    }
}
public class Synchronization {
    public static void main(String[] args) {
        MathService mathService = new MathService();
        MathService mathService1 = new MathService();

        Thread threadOne = new Thread(() ->
                mathService.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadTwo = new Thread(() ->
                mathService.getSumOfArray(new int[]{20, 21, 22}));
        Thread threadThree = new Thread(() ->
                mathService1.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadFour = new Thread(() ->
                mathService1.getSumOfArray(new int[]{20, 21, 22}));

        threadOne.start();
        threadTwo.start();
        threadThree.start();
        threadFour.start();
    }
}

运行上述代码,并注意我们现在已经消除了线程干扰。代码输出如下所示。

输出

Thread-0 adds 0 to 10 to get -> 10
Thread-0 adds 10 to 11 to get -> 21
Thread-0 adds 21 to 12 to get -> 33
Thread-3 adds 0 to 20 to get -> 20
Thread-3 adds 20 to 21 to get -> 41
Thread-3 adds 41 to 22 to get -> 63
Thread-2 adds 0 to 10 to get -> 10
Thread-2 adds 10 to 11 to get -> 21
Thread-2 adds 21 to 12 to get -> 33
Thread-1 adds 0 to 20 to get -> 20
Thread-1 adds 20 to 21 to get -> 41
Thread-1 adds 41 to 22 to get -> 63

使用同步的优点

以下是处理并发应用程序时的优点

  • Java 同步的主要目标是通过防止线程干扰来防止数据不一致。
  • Java 中的 `synchronized` 关键字提供了锁定,确保对共享资源的互斥访问并防止数据争用。
  • 它还阻止了 编译器 对代码语句的重新排序,如果我们不使用 volatile 或 synchronized 关键字,这可能会导致微妙的并发问题。
  • 同步关键字从主内存读取数据,而不是从缓存读取,然后在它释放锁。
  • 它还刷新主内存中的写入操作,从而消除内存不一致错误。

同步机制的缺点

同步机制的性能较差。

例如

  • 假设有五个进程,A1、A2、A3、A4 和 A5。
  • 它们正在等待共享资源,一次只有一个线程可以访问。
  • 所有进程都处于等待状态,因此队列中的最后一个进程必须等到所有其他进程都完成。

摘要

  • 同步是指控制多个线程访问任何共享资源的能力。
  • Java 有两种同步方法:1) 进程同步和 2) 线程同步。
  • Java 中的锁是围绕一个称为监视器或锁的内部实体构建的。
  • 多线程程序是使用 `synchronized` 关键字指示的、受保护的、不受共享相同资源的任何其他线程干扰的方法或代码块。
  • 任何声明为同步的方法都称为同步方法。
  • 在 Java 中,同步方法的锁在方法上访问,而同步代码块的锁在对象上访问。
  • 在静态同步中,锁访问的是类,而不是对象和方法。
  • Java 同步的主要目标是通过防止线程干扰来防止数据不一致。
  • 这种方法最大的缺点是所有进程都处于等待状态,因此队列中的最后一个进程必须等到所有其他进程都完成。