线程主要通过共享对字段和引用字段引用的对象的访问来进行通信。这种形式的通信非常有效,但会导致两种错误:线程干扰和内存一致性错误。需要一些同步结构来防止这些错误。下面的示例显示了需要同步的情况。
需要同步
考虑下面的例子:
// 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
在上述示例中,执行三个操作:
- 获取变量i的值。
- 增加获取的值。
- 并将增加的i值存储到其位置。
在这里
- 第一个线程获取i的值(当前值i为0)并将其增加1,因此变量i的值变为1。
- 现在,第二个线程访问i的值,该值将为0,因为第一个线程没有将其存储回其位置。 第二个线程也会增加它,并将其存储回其位置。而且还要储存起来。
- 变量i的最终值为1。但这两个线程的作用应该是2。这就是为什么我们需要同步对共享变量i的访问。
Java是多线程语言,多个线程并行运行以完成它们的执行。我们需要同步共享资源,以确保一次只有一个线程能够访问共享资源。 如果一个对象由多个线程共享,那么就需要进行同步,以避免对象的状态被破坏。当对象是可变的时,需要同步。如果共享对象是不可变的,或者共享同一对象的所有线程只读取对象的状态,而不进行修改,则不需要对其进行同步。
Java编程语言提供了两种同步习惯用法:
- 方法同步
- 语句同步(块同步)
方法同步
同步方法提供了一种简单的策略来防止线程干扰和内存一致性错误。如果一个对象对多个线程可见,则对该对象字段的所有读取或写入都通过 同步的 方法
同步方法的两次调用不可能交错。如果一个线程正在执行synchronized方法,那么在同一个对象上调用synchronized方法的所有其他线程都必须等待,直到第一个线程处理完该对象。
示例:这显示了如果多个线程在不同步的情况下访问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行代码方法,以避免对象状态中的任何修改,并确保其他线程可以在同一方法中执行其余的代码行,而不会出现任何中断。
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); } } |
输出:
mohit
要点:
- 当线程进入同步方法或块时,它会获得锁,一旦完成任务并退出同步方法,它就会释放锁。
- 当线程进入同步实例方法或块时,它获取对象级锁;当线程进入同步静态方法或块时,它获取类级锁。
- 如果同步块中使用的对象为null,Java同步将引发null指针异常。例如,如果在 已同步(实例) , 例子 如果为null,则会引发null指针异常。
- 在爪哇, wait()、notify()和notifyAll() 是同步中使用的重要方法。
- 你不能应用java 同步的 关键字和变量。
- 不要在同步块上的非最终字段上同步,因为对非最终字段的引用可能会随时更改,然后不同的线程可能会在不同的对象上同步,即根本不同步。
优势
- 多线程: 由于java是多线程语言,同步是在共享资源上实现互斥的好方法。
- 实例和静态方法: 同步实例方法和同步静态方法都可以并发执行,因为它们用于锁定不同的对象。
局限性
- 并发限制: Java同步不允许并发读取。
- 降低效率: Java synchronized方法运行速度非常慢,可能会降低性能,所以在绝对必要时应该同步该方法,否则就不同步,并且只对代码的关键部分同步块。
本文由 尼茨海伦德拉 .如果你喜欢GeekSforgek,并想贡献自己的力量,你也可以使用 贡献极客。组织 或者把你的文章寄到contribute@geeksforgeeks.org.看到你的文章出现在Geeksforgeks主页上,并帮助其他极客。
如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。