20-05-27
banq
在本文中,您将学习为什么我们需要Java同步,如何编写同步的代码以及有关同步的更重要的要点。
为什么要使用同步?
如果您的代码在多线程环境中运行,则需要同步在多个线程之间共享的对象。否则,可能会发生两种类型的错误。
- 线程干扰错误
- 内存一致性错误
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应用程序更加容易。