Lock -- 01 -- synchronized的基本概念

原文链接:Thread – 10 – synchronized的基本概念


相关文章:

  • Lock – 01 – synchronized的基本概念

  • Lock – 02 – synchronized底层实现原理

  • Lock – 03 – synchronized的优化

  • Lock – 04 – ReentrantLock底层实现原理


在 Java 多线程编程中,我们需要重点关注一个问题,那就是线程安全问题,造成线程安全的原因,有以下两点

  • 存在共享数据 (也称为临界资源)

  • 存在多个线程共同操作共享数据

为了解决该问题,我们需要引进这么一个方法

  • 当存在多个线程共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等待该线程处理完数据后再进行

此时,便引入了互斥锁的概念,其有以下两点特性

  • 互斥性

    • 在同一时间内只允许一个线程持有某个锁,通过这种特性来实现多线程的协调机制,这样才能确保在同一时间内只有一个线程对需要同步的代码块 (复合操作) 进行访问

    • 互斥性也称为操作的原子性

  • 可见性

    • 必须确保在锁被释放之前,对共享变量所作的修改,对于随后获得该锁的另一个线程是可见的 (即在获得锁时应获得共享变量最新的值),否则另一个线程可能在本地缓存的某个副本上继续操作,从而引起数据的不一致

对于 Java 而言,关键字 synchronized 满足了上述的要求


一、synchronized 的三种应用方式

  • 修饰实例方法

    • synchronized method() (对象锁)

    • 作用于当前实例对象加锁,进入同步区域需要获得当前实例对象的锁

  • 修饰代码块

    • synchronized(this) (对象锁)

    • synchronized(实例对象) (对象锁)

    • synchronized(类.class) (类锁)

    • 作用于指定对象加锁,进入同步区域需要获得指定对象的锁

  • 修饰静态方法

    • synchronized static method() (类锁)

    • 作用于当前类对象加锁,进入同步区域需要获得当前类对象的锁


二、举例说明

  • 修饰实例方法

    • synchronized method() (对象锁)

      public class SyncThread implements Runnable {
      
          @Override
          public void run() {
              syncObjectMethod();
          }
          
          private synchronized void syncObjectMethod() {
              System.out.println(Thread.currentThread().getName() + "_syncObjectMethod: : " 
                  + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
              try {
                  System.out.println(Thread.currentThread().getName() + "_syncObjectMethod_Start: " 
                      + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                  TimeUnit.SECONDS.sleep(1);
                  System.out.println(Thread.currentThread().getName() + "_syncObjectMethod_End: " 
                      + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      
      public class SyncDemo {
      
          public static void main(String[] args) {
              SyncThread syncThread = new SyncThread();
              Thread thread1 = new Thread(syncThread, "thread1");
              Thread thread2 = new Thread(syncThread, "thread2");
              thread1.start();
              thread2.start();
              
              // thread1_syncObjectMethod: : 15:28:36
              // thread1_syncObjectMethod_Start: 15:28:36
              // thread1_syncObjectMethod_End: 15:28:37
              // thread2_syncObjectMethod: : 15:28:37
              // thread2_syncObjectMethod_Start: 15:28:37
              // thread2_syncObjectMethod_End: 15:28:38
          }
      }
      
      • 如上所示,synchronized 用于修饰实例方法,锁住的是同一个 syncThread 对象

      • 整个实例方法内的逻辑会同步执行

      • 因此当一个线程访问对象的同步方法时,另外访问该对象同步方法的线程将会被阻塞


  • 修饰代码块

    • synchronized(this) (对象锁)

      public class SyncThread implements Runnable {
      
          @Override
          public void run() {
              syncThisBlock();
          }
      
          private void syncThisBlock() {
              System.out.println(Thread.currentThread().getName() + "_syncThisBlock: : " 
                  + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
              synchronized (this) {
                  try {
                      System.out.println(Thread.currentThread().getName() + "_syncThisBlock_Start: " 
                          + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                      TimeUnit.SECONDS.sleep(1);
                      System.out.println(Thread.currentThread().getName() + "_syncThisBlock_End: " 
                          + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      
      public class SyncDemo {
      
          public static void main(String[] args) {
              SyncThread syncThread = new SyncThread();
              Thread thread1 = new Thread(syncThread, "thread1");
              Thread thread2 = new Thread(syncThread, "thread2");
              thread1.start();
              thread2.start();
              
              // thread1_syncThisBlock: : 15:40:24
              // thread2_syncThisBlock: : 15:40:24
              // thread1_syncThisBlock_Start: 15:40:24
              // thread1_syncThisBlock_End: 15:40:25
              // thread2_syncThisBlock_Start: 15:40:25
              // thread2_syncThisBlock_End: 15:40:26
          }
      }
      
      • 如上所示,synchronized 用于修饰代码块,锁住的是同一个 this (即 syncThread 对象)

      • 整个实例方法内,未在同步代码块内的逻辑会异步执行,在同步代码块内的逻辑会同步执行

      • 因此当一个线程访问对象的同步代码块时,另外访问该对象同步代码块的线程将会被阻塞


    • synchronized(实例对象) (对象锁)

      public class SyncThread implements Runnable {
      
          private Object lock = new Object();
      
          @Override
          public void run() {
              syncObjectBlock();
          }
      
          private void syncObjectBlock() {
              System.out.println(Thread.currentThread().getName() + "_syncObjectBlock: : " 
                  + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
              synchronized (lock) {
                  try {
                      System.out.println(Thread.currentThread().getName() + "_syncObjectBlock_Start: " 
                          + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                      TimeUnit.SECONDS.sleep(1);
                      System.out.println(Thread.currentThread().getName() + "_syncObjectBlock_End: " 
                          + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      
      public class SyncDemo {
      
          public static void main(String[] args) {
              SyncThread syncThread = new SyncThread();
              Thread thread1 = new Thread(syncThread, "thread1");
              Thread thread2 = new Thread(syncThread, "thread2");
              thread1.start();
              thread2.start();
              
              // thread2_syncObjectBlock: : 16:11:44
              // thread1_syncObjectBlock: : 16:11:44
              // thread2_syncObjectBlock_Start: 16:11:44
              // thread2_syncObjectBlock_End: 16:11:45
              // thread1_syncObjectBlock_Start: 16:11:45
              // thread1_syncObjectBlock_End: 16:11:46
          }
      }
      
      • 如上所示,synchronized 用于修饰代码块,锁住的是同一个 lock 对象

      • 此外,锁住的 lock 对象必须为同一个对象,否则就会异步执行代码块了

      • 整个实例方法内,未在同步代码块内的逻辑会异步执行,在同步代码块内的逻辑会同步执行

      • 因此当一个线程访问对象的同步代码块时,另外访问该对象同步代码块的线程将会被阻塞


    • synchronized(类.class) (类锁)

      public class SyncThread implements Runnable {
      
          @Override
          public void run() {
              syncClassBlock();
          }
      
          private void syncClassBlock() {
              System.out.println(Thread.currentThread().getName() + "_syncClassBlock: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
              synchronized (SyncThread.class) {
                  try {
                      System.out.println(Thread.currentThread().getName() + "_syncClassBlock_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                      TimeUnit.SECONDS.sleep(1);
                      System.out.println(Thread.currentThread().getName() + "_syncClassBlock_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      
      public class SyncDemo {
      
          public static void main(String[] args) {
              SyncThread syncThread = new SyncThread();
              Thread thread1 = new Thread(syncThread, "thread1");
              Thread thread2 = new Thread(syncThread, "thread2");
              thread1.start();
              thread2.start();
              
              // thread1_syncClassBlock: : 16:23:27
              // thread2_syncClassBlock: : 16:23:27
              // thread1_syncClassBlock_Start: 16:23:28
              // thread1_syncClassBlock_End: 16:23:29
              // thread2_syncClassBlock_Start: 16:23:29
              // thread2_syncClassBlock_End: 16:23:30
          }
      }
      
      public class SyncDemo {
      
          public static void main(String[] args) {
              Thread thread1 = new Thread(new SyncThread(), "thread1");
              Thread thread2 = new Thread(new SyncThread(), "thread2");
              thread1.start();
              thread2.start();
              
              // thread2_syncClassBlock: : 16:24:47
              // thread1_syncClassBlock: : 16:24:47
              // thread2_syncClassBlock_Start: 16:24:47
              // thread2_syncClassBlock_End: 16:24:49
              // thread1_syncClassBlock_Start: 16:24:49
              // thread1_syncClassBlock_End: 16:24:50
          }
      }
      
      • 如上所示,synchronized 用于修饰代码块,锁住的是同一个 class 对象

      • 同一个类的不同对象使用类锁将会是同步的

      • 整个实例方法内,未在同步代码块内的逻辑会异步执行,在同步代码块内的逻辑会同步执行

      • 因此当一个线程访问对象的同步代码块时,另外访问该对象同步代码块的线程将会被阻塞


  • 修饰静态方法

    • synchronized static method() (类锁)

      public class SyncThread implements Runnable {
      
          @Override
          public void run() {
              syncClassMethod();
          }
      
          private synchronized static void syncClassMethod() {
              System.out.println(Thread.currentThread().getName() + "_syncClassMethod: : " 
                  + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
              try {
                  System.out.println(Thread.currentThread().getName() + "_syncClassMethod_Start: " 
                      + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                  TimeUnit.SECONDS.sleep(1);
                  System.out.println(Thread.currentThread().getName() + "_syncClassMethod_End: " 
                      + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      
      public class SyncDemo {
      
          public static void main(String[] args) {
              SyncThread syncThread = new SyncThread();
              Thread thread1 = new Thread(syncThread, "thread1");
              Thread thread2 = new Thread(syncThread, "thread2");
              thread1.start();
              thread2.start();
              
              // thread1_syncClassMethod: : 16:30:45
              // thread1_syncClassMethod_Start: 16:30:45
              // thread1_syncClassMethod_End: 16:30:46
              // thread2_syncClassMethod: : 16:30:46
              // thread2_syncClassMethod_Start: 16:30:46
              // thread2_syncClassMethod_End: 16:30:47
          }
      }
      
      • 如上所示,synchronized 用于修饰静态方法,锁住的是同一个 class 对象

      • 整个静态方法内的逻辑会同步执行

      • 因此当一个线程访问对象的同步方法时,另外访问该对象同步方法的线程将会被阻塞


三、对比说明

  • synchronized method() (对象锁)synchronized(this) (对象锁) 进行比较

    public class SyncThread implements Runnable {
    
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            if (threadName.startsWith("A")) {
                syncThisBlock();
            } else if (threadName.startsWith("B")) {
                syncObjectMethod();
            }
        }
    
        private void syncThisBlock() {
            System.out.println(Thread.currentThread().getName() + "_syncThisBlock: : "
                    + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
            synchronized (this) {
                try {
                    System.out.println(Thread.currentThread().getName() + "_syncThisBlock_Start: "
                            + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName() + "_syncThisBlock_End: "
                            + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        private synchronized void syncObjectMethod() {
            System.out.println(Thread.currentThread().getName() + "_syncObjectMethod: : "
                    + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
            try {
                System.out.println(Thread.currentThread().getName() + "_syncObjectMethod_Start: "
                        + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "_syncObjectMethod_End: "
                        + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public class SyncDemo {
    
        public static void main(String[] args) {
            SyncThread syncThread = new SyncThread();
            Thread A_thread = new Thread(syncThread, "A_thread");
            Thread B_thread = new Thread(syncThread, "B_thread");
            A_thread.start();
            B_thread.start();
            
            // A_thread_syncThisBlock: : 17:04:40
            // B_thread_syncObjectMethod: : 17:04:40
            // B_thread_syncObjectMethod_Start: 17:04:41
            // B_thread_syncObjectMethod_End: 17:04:42
            // A_thread_syncThisBlock_Start: 17:04:42
            // A_thread_syncThisBlock_End: 17:04:43
        }
    }
    
    • 如上所示,synchronized 分别用来修饰 this 和实例方法,即两方法锁住的是同一个 syncThread 对象

    • 因此当一个线程访问对象的同步代码块时,另外访问该对象同步方法的线程将会被阻塞,反之亦然


  • synchronized(类.class) (类锁)synchronized static method() (类锁) 进行比较

    public class SyncThread implements Runnable {
    
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            if (threadName.startsWith("A")) {
                syncClassBlock();
            } else if (threadName.startsWith("B")) {
                syncClassMethod();
            }
        }
    
        private void syncClassBlock() {
            System.out.println(Thread.currentThread().getName() + "_syncClassBlock: : " 
                + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
            synchronized (SyncThread.class) {
                try {
                    System.out.println(Thread.currentThread().getName() + "_syncClassBlock_Start: " 
                        + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName() + "_syncClassBlock_End: "
                        + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        private synchronized static void syncClassMethod() {
            System.out.println(Thread.currentThread().getName() + "_syncClassMethod: : " 
                + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
            try {
                System.out.println(Thread.currentThread().getName() + "_syncClassMethod_Start: " 
                    + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "_syncClassMethod_End: " 
                    + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public class SyncDemo {
    
        public static void main(String[] args) {
            SyncThread syncThread = new SyncThread();
            Thread A_thread = new Thread(syncThread, "A_thread");
            Thread B_thread = new Thread(syncThread, "B_thread");
            A_thread.start();
            B_thread.start();
    
            // A_thread_syncClassBlock: : 17:11:04
            // B_thread_syncClassMethod: : 17:11:04
            // B_thread_syncClassMethod_Start: 17:11:04
            // B_thread_syncClassMethod_End: 17:11:05
            // A_thread_syncClassBlock_Start: 17:11:05
            // A_thread_syncClassBlock_End: 17:11:06
        }
    }
    
    • 如上所示,synchronized 分别用来修饰 类.class 和静态方法,即两方法锁住的是同一个 Class 对象

    • 因此当一个线程访问对象的同步代码块时,另外访问该对象同步方法的线程将会被阻塞,反之亦然


四、归纳总结

  • 当一个线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块

  • 若锁住的是同一个对象,则当一个线程访问对象的同步方法时,另外访问该对象同步方法的线程将会被阻塞

  • 若锁住的是同一个对象,则当一个线程访问对象的同步代码块时,另外访问该对象同步代码块的线程将会被阻塞

  • 若锁住的是同一个对象,则当一个线程访问对象的同步代码块时,另外访问该对象同步方法的线程将会被阻塞,反之亦然

  • 同一个类的不同对象的对象锁互不干扰

  • 由于类锁也是一种特殊的对象锁,因此表现和上述 1、2、3、4 点一致;而由于一个类只会有一把对象锁 (类锁),所以同一个类的不同对象使用类锁将会是同步的

  • 类锁和对象锁互不干扰


五、参考资料

  • 深入理解Java并发之synchronized实现原理

你可能感兴趣的:(Thread)