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

在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,因为第一个线程没有将它存储回它的位置。
    第二个线程也会增加它并将其存储回其位置。第1个也存储它。
  • 最后,变量i的值为1.但是两个线程的效果应该是2。这就是为什么我们需要同步对共享变量i的访问。

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

Java编程语言提供两种同步习语:

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

Java中的方法同步

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

两次调用同步方法不可能进行交错。如果一个线程正在执行synchronized方法,那么在同一个Object上调用synchronized方法的所有其他线程都必须等到第一个线程完成Object。

示例:这显示了多个线程是否在没有同步的情况下访问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

Java中的块同步

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

import java.io.*;
import java.util.*;

public class Geek
{
    String name = "";
    public int count = 0;

    public void geekName(String geek, List<String> 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<String> list = new ArrayList<String>();
        gk.geekName("mohit", list);
        System.out.println(gk.name);

    }
}

输出:

1

重点:

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

优点

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

限制

  • 并发限制: Java同步不允许并发读取。
  • 降低效率: Java同步方法运行速度非常慢并且会降低性能,因此您应该在绝对必要时同步方法,否则不要同步块仅用于代码的关键部分。
 
转载请保留页面地址:https://www.breakyizhan.com/java/4963.html
扫描二维码添加微信 
  • ,每次淘宝领取淘宝优惠券,淘宝购物更优惠。现在添加微信,还可以领取机械键盘50元优惠券!添加微信后回复机械键盘即可领取!
    支持我们,就用微信淘宝!