📜  Java中的方法和块同步

📅  最后修改于: 2022-05-13 01:55:32.503000             🧑  作者: Mango

Java中的方法和块同步

线程主要通过共享对字段和对象引用字段的访问来进行通信。这种通信形式非常高效,但会导致两种错误:线程干扰和内存一致性错误。需要一些同步结构来防止这些错误。以下示例显示了我们需要同步的情况。

同步的需要

考虑以下示例:

// Java program to illustrate need
// of Synchronization
import java.io.*;
  
class Multithread
{
    private int i = 0;
    public void increment()
    {
        i++;
    }
  
    public int getValue()
    {
        return i;
    }
}
  
class GfG
{
    public static void main (String[] args)
    {
        Multithread t = new Multithread();
        t.increment();
        System.out.println(t.getValue());
    }
}

输出:

1

在上面的示例中,执行了三个操作:

  1. 获取变量 i 的值。
  2. 增加获取的值。
  3. 并将 i 的增加值存储到它的位置。

这里,

  • 第一个线程获取 i 的值。 (当前值 i 为 0)并将其增加 1,因此变量 i 的值变为 1。
  • 现在第二个线程访问 i 的值为 0,因为第一个线程没有将它存储回它的位置。
    第二个线程也会增加它并将其存储回它的位置。第一个也存储它。
  • 变量 i 的最终值为 1。但由于两个线程的影响,它应该为 2。这就是为什么我们需要同步对共享变量 i 的访问。

Java是多线程语言,其中多个线程并行运行以完成它们的执行。我们需要同步共享资源,以确保一次只有一个线程能够访问共享资源。
如果一个对象被多个线程共享,则需要同步以避免对象的状态被破坏。当 Object 可变时需要同步。如果共享对象是不可变的,或者共享同一对象的所有线程只读取对象的状态而不修改,那么您不需要同步它。

Java编程语言提供了两种同步习惯用法:

  • 方法同步
  • 语句同步(块同步)

方法同步

同步方法启用了防止线程干扰和内存一致性错误的简单策略。如果一个对象对多个线程可见,则对该对象字段的所有读取或写入都通过同步方法完成。

同步方法的两次调用不可能交错。如果一个线程正在执行同步方法,则在同一对象上调用同步方法的所有其他线程将不得不等到第一个线程完成该对象。

示例:这表明是否有多个线程在没有同步的情况下访问 getLine() 方法。

// Example illustrates multiple threads are executing
// on the same Object at same time without synchronization.
import java.io.*;
  
class Line
{
    // if multiple threads(trains) will try to
    // access this unsynchronized method,
    // they all will get it. So there is chance
    // that Object's  state will be corrupted.
    public void getLine()
    {
        for (int i = 0; i < 3; i++)
        {
            System.out.println(i);
            try
            {
                Thread.sleep(400);
            }
            catch (Exception e)
            {
                System.out.println(e);
            }
        }
    }
}
  
class Train extends Thread
{
    // reference to Line's Object.
    Line line;
  
    Train(Line line)
    {
        this.line = line;
    }
  
    @Override
    public void run()
    {
        line.getLine();
    }
}
  
class GFG
{
    public static void main(String[] args)
    {
        // Object of Line class that is shared
        // among the threads.
        Line obj = new Line();
  
        // creating the threads that are
        // sharing the same Object.
        Train train1 = new Train(obj);
        Train train2 = new Train(obj);
  
        // threads start their execution.
        train1.start();
        train2.start();
    }
}

输出

0
0
1
1
2
2

可能有两列火车(多于两列)需要同时使用相同的火车,因此有可能发生碰撞。因此,为了避免冲突,我们需要同步多个要运行的行。

示例:同步访问同一对象上的 getLine() 方法

// Example that shows multiple threads
// can execute the same method but in
// synchronized way.
class Line
{
  
    // if multiple threads(trains) trying to access
    // this synchronized method on the same Object
    // but only one thread will be able
    // to execute it at a time.
    synchronized public void getLine()
    {
        for (int i = 0; i < 3; i++)
        {
            System.out.println(i);
            try
            {
                Thread.sleep(400);
            }
            catch (Exception e)
            {
                System.out.println(e);
            }
        }
    }
}
  
class Train extends Thread
{
    // Reference variable of type Line.
    Line line;
  
    Train(Line line)
    {
        this.line = line;
    }
  
    @Override
    public void run()
    {
        line.getLine();
    }
}
  
class GFG
{
    public static void main(String[] args)
    {
        Line obj = new Line();
  
        // we are creating two threads which share
        // same Object.
        Train train1 = new Train(obj);
        Train train2 = new Train(obj);
  
        // both threads start executing .
        train1.start();
        train2.start();
    }
}

输出:

0
1
2
0
1
2

块同步

如果我们只需要执行一些后续代码行而不是方法中的所有代码行(指令),那么我们应该只同步存在所需指令的代码块。
例如,假设有一个方法包含 100 行代码,但只有 10 行(一个接一个)包含关键代码部分的代码,即这些行可以修改(更改)对象的状态。所以我们只需要同步这 10 行代码方法即可,避免 Object 的状态发生任何修改,并确保其他线程可以在同一方法中执行其余行而不会中断。

import java.io.*;
import java.util.*;
  
public class Geek
{
    String name = "";
    public int count = 0;
  
    public void geekName(String geek, List list)
    {
        // Only one thread is permitted
        // to change geek's name at a time.
        synchronized(this)
        {
            name = geek;
            count++;  // how many threads change geek's name.
        }
  
        // All other threads are permitted
        // to add geek name into list.
        list.add(geek);
    }
}
  
class GFG
{
    public static void main (String[] args)
    {
        Geek gk = new Geek();
        List list = new ArrayList();
        gk.geekName("mohit", list);
        System.out.println(gk.name);
  
    }
}

输出 :

mohit

要点:

  • 当线程进入同步方法或块时,它会获取锁,一旦完成任务并退出同步方法,它就会释放锁。
  • 当线程进入同步实例方法或块时,它会获得对象级锁,当它进入同步静态方法或块时,它会获得类级锁。
  • 如果同步块中使用的对象为空, Java同步将抛出空指针异常。例如,如果在synchronized(instance)中, instance为 null,那么它会抛出空指针异常。
  • 在Java中, wait()、notify() 和 notifyAll()是同步中使用的重要方法。
  • 您不能将Java synchronized关键字与变量一起应用。
  • 不要在同步块上的非最终字段上同步,因为对非最终字段的引用可能随时更改,然后不同的线程可能会在不同的对象上同步,即根本没有同步。

好处

  • 多线程:由于Java是多线程语言,同步是实现共享资源互斥的好方法。
  • 实例和静态方法:同步实例方法和同步静态方法都可以并发执行,因为它们用于锁定不同的对象。

限制

  • 并发限制: Java同步不允许并发读取。
  • 降低效率: Java同步方法运行非常缓慢并且会降低性能,因此您应该在绝对必要时同步该方法,否则不要同步,并且仅对代码的关键部分进行同步块。