• Tags         
  • 2018-07-19  16:44:19        
  • 79 °C    

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

    在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