📜  Java原子,易失和同步之间的区别

📅  最后修改于: 2021-09-15 01:46:16             🧑  作者: Mango

Synchronized 是仅适用于方法和块而不适用于变量和类的修饰符。可能存在数据不一致问题来克服这个问题,当多个线程试图同时操作同一个Java对象时,我们应该使用 synchronized 关键字。如果一个方法或块声明为同步的,那么一次只允许一个线程在给定的对象上执行该方法或块,以便解决数据不一致问题。 synchronized 关键字的主要优点是我们可以解决数据不一致的问题,但该关键字的主要缺点是增加了线程的等待时间并产生了性能问题。因此,在没有特定要求的情况下,不建议使用 synchronized 关键字。 Java中的每个对象都有一个唯一的锁。当我们使用同步关键字时,锁的概念就会出现。

当一个线程在给定对象上执行同步方法时,其余线程不允许在同一对象上同时执行任何同步方法。但允许剩余线程同时执行非同步方法。

挥发性修饰符:

如果一个变量的值不断被多个线程改变,那么就有可能出现数据不一致的问题。它是一个仅适用于变量的修饰符,我们不能在其他任何地方应用它。我们可以通过使用 volatile 修饰符来解决这个问题。如果一个变量被声明为 volatile ,对于每个线程JVM将创建一个单独的本地副本。线程执行的每次修改都将在本地副本中进行,因此不会对其余线程产生影响。克服数据不一致问题是优点,而 volatile 关键字为每个线程创建和维护单独的副本增加了编程的复杂性并产生了性能问题是缺点。因此,如果没有特定要求,则永远不建议使用 volatile 关键字。

原子修饰符:

如果一个变量的值不断被多个线程改变,那么就有可能出现数据不一致的问题。我们可以通过使用原子变量来解决这个问题。当这些类的对象分别代表int、long、boolean和对象引用的原子变量时,可以解决数据不一致问题。

例子:

在下面的示例中,每个线程将计数变量增加 5 次。所以两个线程执行完后,完成计数值应该是10。

Java
// import required packages
import java.io.*;
import java.util.*;
  
// creatimg a thread by extending a thread class
class myThread extends Thread {
    
    // declaring a count variable
    private int count;
  
    public void run()
    {
        // calculating the count
        for (int i = 1; i <= 5; i++) {
  
            try {
                Thread.sleep(i * 100);
                count++;
            }
            catch (InterruptedException
                       e) { // throwing an exception
                System.out.println(e);
            }
        }
    }
    // returning the count value
    public int getCount() { return this.count; }
}
// driver class
public class GFG {
    
    // main method
    public static void main(String[] args)
        throws InterruptedException
    {
  
        // creating an thread object
        myThread t = new myThread();
        Thread t1 = new Thread(t, "t1");
        
        // starting thread t1
        t1.start();
        Thread t2 = new Thread(t, "t2");
        
        // starting thread t2
        t2.start();
        
        // calling join method on thread t1
        t1.join();
        
        // calling join method on thread t1
        t2.join();
        
        // displaying the count
        System.out.println("count=" + t.getCount());
    }
}


Java
// import required packages
import java.io.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
  
// creatimg a thread by extending a thread class
class myThread extends Thread {
    
    // declaring an atomic variable
    private AtomicInteger count = new AtomicInteger();
  
    public void run()
    {
        // calculating the count
        for (int i = 1; i <= 5; i++) {
            try {
                
                // putting  thread on sleep
                Thread.sleep(i * 100);
                
                // calling incrementAndGet() method
                // on count variable
                count.incrementAndGet();
            }
            catch (InterruptedException e) {
                
                // throwing exception
                System.out.println(e);
            }
        }
    }
    // returnng the count value
    public AtomicInteger getCount() { return count; }
}
// driver class
public class GFG {
    
    // main method
    public static void main(String[] args)
        throws InterruptedException
    {
        // creating an thread object
        myThread t = new myThread();
        
        Thread t1 = new Thread(t, "t1");
        
        // starting thread t1
        t1.start();
        
        Thread t2 = new Thread(t, "t2");
        
        // starting thread t2
        t2.start();
        
        // calling join method on thread t1
        t1.join();
        
        // calling join method on thread t1
        t2.join();
        
        // displaying the count
        System.out.println("count=" + t.getCount());
    }
}


输出
count=10

如果我们运行上面的程序,我们会注意到count值在6,7,8.9之间变化,原因是count++不是原子操作。因此,当一个线程读取它的值并将其递增 1 时,另一个线程已经读取了导致错误结果的旧值。为了解决这个问题,我们必须确保计数的递增操作是原子的。

下面的程序将始终输出计数值为 8,因为 AtomicInteger 方法 incrementAndGet() 以原子方式将当前值递增 1。

Java

// import required packages
import java.io.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
  
// creatimg a thread by extending a thread class
class myThread extends Thread {
    
    // declaring an atomic variable
    private AtomicInteger count = new AtomicInteger();
  
    public void run()
    {
        // calculating the count
        for (int i = 1; i <= 5; i++) {
            try {
                
                // putting  thread on sleep
                Thread.sleep(i * 100);
                
                // calling incrementAndGet() method
                // on count variable
                count.incrementAndGet();
            }
            catch (InterruptedException e) {
                
                // throwing exception
                System.out.println(e);
            }
        }
    }
    // returnng the count value
    public AtomicInteger getCount() { return count; }
}
// driver class
public class GFG {
    
    // main method
    public static void main(String[] args)
        throws InterruptedException
    {
        // creating an thread object
        myThread t = new myThread();
        
        Thread t1 = new Thread(t, "t1");
        
        // starting thread t1
        t1.start();
        
        Thread t2 = new Thread(t, "t2");
        
        // starting thread t2
        t2.start();
        
        // calling join method on thread t1
        t1.join();
        
        // calling join method on thread t1
        t2.join();
        
        // displaying the count
        System.out.println("count=" + t.getCount());
    }
}
输出
count=10
                     Synchronized                       Volatile                  Atomic
1.It is applicable to only blocks or methods. 1.It is applicable to variables only. 1.It is also applicable to variables only.
2. Synchronized modifier is used to implement a lock-based concurrent algorithm, and i.e it suffers from the limitation of locking. 2.Whereas Volatile gives the power to implement a non-blocking algorithm that is more scalable. 2.Atomic also gives the power to implement the non-blocking algorithm.
3.Performance is relatively low compare to volatile and atomic keywords because of the acquisition and release of the lock. 3.Performance is relatively high compare to synchronized keyword. 3.Performance is relatively high compare to both volatile and synchronized keyword.
4.Because of its locking nature it is not immune to concurrency hazards such as deadlock and livelock. 4.Because of its non-locking nature it is immune to concurrency hazards such as deadlock and livelock. 4.Because of its non-locking nature it is immune to concurrency hazards such as deadlock and livelock.