synchronized与volatile的限制

synchronized

 1. 性能开销较大:synchronized关键字是通过对对象加锁来实现线程同步的,当一个线程获取锁时,其他线程需要等待该线程释放锁才能继续执行。这种加锁和释放锁的操作会带来一定的性能开销,尤其是在高并发场景下,大量线程竞争同一把锁时,会导致线程频繁地阻塞和唤醒,从而影响系统的性能;
 2. 粒度问题:synchronized关键字的锁粒度比较大,一旦对某个方法或代码块加锁,就会阻止其他线程对该方法或代码块的访问。如果锁的范围过大,会导致并发度降低,影响程序的性能;
 3. 可能导致死锁:当多个线程相互等待对方释放锁时,就会发生死锁。synchronized关键字如果使用不当,很容易导致死锁的发生;

public class DeadlockExample {
   private static final Object lock1 = new Object();
   private static final Object lock2 = new Object();

   public static void main(String[] args) {
       Thread thread1 = new Thread(() -> {
           synchronized (lock1) {
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized (lock2) {
                   System.out.println("Thread 1 acquired both locks");
               }
           }
       });

       Thread thread2 = new Thread(() -> {
           synchronized (lock2) {
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized (lock1) {
                   System.out.println("Thread 2 acquired both locks");
               }
           }
       });

       thread1.start();
       thread2.start();
   }
}

 4. 只能保证原子性和可见性,不能保证有序性:synchronized关键字可以保证同一时刻只有一个线程执行同步代码块,从而保证了原子性;同时,它也能保证一个线程在释放锁之前对共享变量的修改对其他线程是可见的,即保证了可见性。但是,它不能禁止指令重排序,不能保证代码的执行顺序与编写顺序一致。
  仅依靠synchronized本身来保障相对有序性:synchronized块或方法内部代码的操作具有一定的相对有序性,即同一线程在同步块内的操作不会被重排序到同步块外。虽然它不能阻止JVM对同步块内的指令进行重排序,但从多线程交互的角度来看,它可以保证不同线程对共享资源操作的有序执行;
  结合volatile关键字:可以使用volatile变量来作为线程间的通信标志,辅助synchronized实现更严格的有序性;

public class SynchronizedVolatileOrder {
   private int a = 0;
   private volatile boolean flag = false;

   public synchronized void write() {
       a = 1;
       flag = true; 
   }

   public void read() {
       while (!flag) {
           // 等待 write 方法完成
       }
       System.out.println(a); 
   }

   public static void main(String[] args) {
       SynchronizedVolatileOrder obj = new SynchronizedVolatileOrder();
       Thread writer = new Thread(obj::write);
       Thread reader = new Thread(obj::read);
       //flag没有被声明为volatile,可能会出现writer线程的write方法未执行完,reader线程就开始执行后续操作,
       //甚至可能出现reader线程陷入死循环的情况
       writer.start();
       reader.start();
   }
}

  结合JUC中的并发工具类:可以结合java.util.concurrent包中的并发工具类,如CountDownLatch、CyclicBarrier等,来精确控制线程的执行顺序,进而保证操作的有序性。

import java.util.concurrent.CountDownLatch;

public class SynchronizedCountDownLatchOrder {
   private int a = 0;
   private CountDownLatch latch = new CountDownLatch(1);

   public synchronized void write() {
       a = 1;
       latch.countDown(); 
   }

   public void read() {
       try {
           latch.await(); 
           System.out.println(a); 
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }

   public static void main(String[] args) {
       SynchronizedCountDownLatchOrder obj = new SynchronizedCountDownLatchOrder();
       Thread writer = new Thread(obj::write);
       Thread reader = new Thread(obj::read);
       //没有使用CountDownLatch,可能会出现writer线程的write方法未执行完,reader线程就开始执行后续操作,
       //甚至可能出现reader线程陷入死循环的情况
       writer.start();
       reader.start();
   }
}

volatile

 1. 只能保证变量的可见性,不能保证原子性:volatile关键字可以保证一个变量在多个线程之间的可见性,即当一个线程修改了该变量的值,其他线程能立即看到最新的值。但是,它不能保证对变量的操作是原子的。
 2. 不能用于修饰方法和代码块:volatile关键字只能用于修饰变量,不能用于修饰方法和代码块。它的作用是保证变量的可见性,而方法和代码块的同步需要使用synchronized关键字或其他同步机制。
 3. 不适合复杂的同步场景:volatile关键字只提供了简单的可见性保证,对于一些复杂的同步场景,如需要保证多个操作的原子性、需要进行锁的重入等,volatile关键字无法满足需求,需要使用synchronized关键字或其他高级同步工具。
  使用synchronized关键字;
  使用Atomic类:java.util.concurrent.atomic包提供了一系列的原子类,如AtomicInteger、AtomicLong等。这些类使用了底层的CAS操作来保证对变量的操作是原子的;
  使用Lock接口及其实现类:java.util.concurrent.locks包中的Lock接口及其实现类提供了比synchronized更灵活的锁机制。通过显式地加锁和解锁,可以保证对共享变量的操作是原子的。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容