如何在Java中使用同步 -Manusha


在本文中,您将学习为什么我们需要Java同步,如何编写同步的代码以及有关同步的更重要的要点。

为什么要使用同步?
如果您的代码在多线程环境中运行,则需要同步在多个线程之间共享的对象。否则,可能会发生两种类型的错误。

  1. 线程干扰错误
  2. 内存一致性错误

Java中的线程干扰是一种情况,当多个同时运行的线程可以访问同一数据时,就会发生这种情况。当线程对同一数据执行不同的操作时,这些操作可能会重叠并在内存中创建不一致的数据。发生这种情况时,数据可能会丢失,损坏或显示意外行为。
在多线程中,一个线程所做的更改可能对其他线程不可见,并且它们对同一共享数据的视图不一致。这称为内存一致性错误。
因此,我们在代码中使用了同步,以避免因这些错误而头痛。您不需要或使用同步共享,如果对象是不可变的,如果所有的线程只能执行只读操作。现在,让我们看看如何同步我们的Java代码。

方法同步
您可以使用synchronized关键字在Java中声明一个同步方法。当线程调用同步方法时,它会在共享数据时自动锁定对象,并在线程完成其任务时释放该对象。因此,如果一个线程运行同步方法,则在同一对象上调用同步方法的所有其他线程将必须等待,直到第一个线程完成。让我们来看一个例子。

class Apple { 
    synchronized public void getApple() { 
        for (int i = 0; i < 3; i++) { 
            System.out.println(i); 
            try
                Thread.sleep(400); 
            } 
            catch (Exception e) { 
                System.out.println(e); 
            } 
        } 
    } 

  
class Tree extends Thread { 
   
    Apple apple; 
  
    Tree (Apple apple) { 
        this.apple = apple; 
    } 
  
    @Override
    public void run() { 
        apple.getApple(); 
    } 

  
public class SyncEx1 { 
    public static void main(String[] args) { 
     
        Apple obj = new Apple(); //Object of Apple class that is shared amoung threads
  
        Tree tree1 = new Tree(obj); 
        Tree tree2 = new Tree(obj); 
  
        tree1.start(); 
        tree2.start(); 
    } 


getApple()方法在示例中是同步的,一次仅允许一个线程访问该方法。因此,该变量的值i将不会不一致。但是,如果我们只想同步一段代码而不是整个方法,该怎么办?那就是我们需要块同步的时候。

块同步
假设您的方法中有50行代码,但是您只想同步5行。同步块允许您同步方法的任何特定部分。它告诉JVM一次仅允许一个线程访问指定的代码段。
类的synchronizedList()方法java.util.Collections用于返回由指定列表支持的同步列表。因此,每当Iterator使用进行迭代同步列表时,Iterator需要同步使用需求的代码块。让我们看一下示例代码。

import java.util.*;
public class SyncEx2{

    public static void main(String []args){
        try{
            List<String> al = new ArrayList<>(); 
            List movieList = Collections.synchronizedList(al);
            movieList.add("StarWars");
            movieList.add(
"Avengers");
            movieList.add(
"Inception");

            synchronized(movieList) {  
// Synchronized block
              Iterator i = movieList.iterator(); 
              while (i.hasNext())
                System.out.println(i.next());
// Print Synchronized movie list
            }
            
            } catch (IllegalArgumentException e) { 
                System.out.println(
"Exception thrown : " + e); 
            } 
    } 
}

有关同步的重要说明

  • 如果同步静态方法,则锁将位于类上,而不是对象上。因此,无论它有多少对象实例,在这个类静态同步方法中都只能运行一个线程。
  • 在同步方法或块中可以使用3种线程间通信方法。这些都是wait(),notify()和notifyAll()。
  • 您可以使用该wait()方法将当前线程设置为释放锁定,然后等待另一个线程调用该对象的一个notify()或多个notifyAll()方法,或者直到指定的时间过去。
  • notify()方法将选择并唤醒在此对象上等待的单个线程,并且该notifyAll()方法将唤醒在该对象的监视器上等待的所有线程。
  • 同步有很多限制,它不允许并发访问,可能会导致死锁,并且同步方法的执行速度非常慢。
  • Java java.util.concurrent在Java 5中引入了该软件包,它可以用作这些问题的解决方案。该软件包提供了一组类,这些类使开发多线程Java应用程序更加容易。