synchronized 同步方法 或者 同步块
互斥锁:即能到达到互斥访问目的的锁
即如果对共享资源加上互斥锁 当一个线程在访问该共享资源时 其他线程便只能等待

在 Java 中 可以使用 synchronized 关键字来标记一个 方法 或者 代码块
当某个线程调用该对象的synchronized方法或者访问synchronized代码块时 这个线程便获得了该对象的锁
其他线程暂时无法访问这个方法 只有等待这个方法执行完毕或者代码块执行完毕
然后这个线程才会释放该对象的锁 其他线程才能执行这个方法或者代码块
![RQPB]JORN14$W_M[V318F.jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/12773302/1622904188409-a8b58ddb-0363-4fca-8d18-ba7f0f153c4d.jpeg#align=left&display=inline&height=470&margin=%5Bobject%20Object%5D&name=RQPB%5DJORN14%24W_M%5BV318F.jpg&originHeight=470&originWidth=804&size=51644&status=done&style=none&width=804)

1、synchronized 同步方法

结构:
public synchronized void method(){

}
需要注意以下三点:
1)当一个线程正在访问一个对象的 synchronized 方法时
其他线程不能访问该对象的其他 synchronized 方法
这是因为一个对象只有一把锁 当一个线程获取了该对象的锁之后 其他线程无法获取该对象的锁 所以无法访问该对象的其他synchronized方法
2)当一个线程正在访问一个对象的 synchronized 方法 那么其他线程能访问该对象的非 synchronized 方法
因为访问非 synchronized 方法不需要获得该对象的锁
假如一个方法没有用 synchronized 关键字修饰 说明它不会使用到共享资源 那么其他线程是可以访问这个方法的
3)如果一个线程 A 需要访问对象 object1 的 synchronized 方法 fun1 另外一个线程 B 需要访问对象 object2 的 synchronized 方法 fun1
即使 object1 和 object2 是同一类型 也不会产生线程安全问题 因为他们访问的是不同的对象 所以不存在互斥问题

2、synchronized 同步块

结构:
synchronized (lock){

}
lock 的取值:
1)this
代表锁的是该类的实例对象
2)Object及其子类类型的 不能是基本类型的
代表锁的是对应类型的实例对象
3)class对象
代表锁的是该类的类对象
注意
1)synchronized方法(非static) 与 synchronized(this) 同步块是互斥的 因为它们锁的是同一个对象
2)但 synchronized方法(非static)与另外两个是异步的 因为它们锁的是不同对象
特别地 每个类也会有一个锁 称之为类锁
对于 static synchronized 方法和 synchronized (xxx.class){ } 锁住的都是类
因为 static 修饰的成员不专属于任何一个对象 是属于类的
如果一个线程执行一个对象的非静态 synchronized 方法 另外一个线程需要执行这个对象所属类的 static synchronized 方法 不会发生互斥现象
因为访问 static synchronized 方法占用的是类锁 而访问非 static synchronized 方法占用的是对象锁 所以不存在互斥现象
总结
1)synchronized 代码块 比 synchronized方法的粒度更细一些 使用起来也灵活得多
因为也许一个方法中只有一部分代码只需要同步 如果此时对整个方法用synchronized进行同步 会影响程序执行效率
而使用synchronized代码块就可以避免这个问题 synchronized代码块可以实现只对需要同步的地方进行同步
2)对于 synchronized 方法 或者 synchronized代码块 当出现异常时 JVM会自动释放当前线程占用的锁 因此不会由于异常导致出现死锁现象

底层原理

我们来看一下synchronized同步方法和同步代码块的底层原理

  1. public class SynchronizedTest {
  2. public synchronized void doSth(){
  3. System.out.println("Hello World");
  4. }
  5. public void doSth1(){
  6. synchronized (SynchronizedTest.class){
  7. System.out.println("Hello World");
  8. }
  9. }
  10. }

我们先来使用Javap来反编译以上代码 结果如下(部分无用信息过滤掉了):

  1. public synchronized void doSth();
  2. descriptor: ()V
  3. flags: ACC_PUBLIC, ACC_SYNCHRONIZED
  4. Code:
  5. stack=2, locals=1, args_size=1
  6. 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  7. 3: ldc #3 // String Hello World
  8. 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  9. 8: return
  10. public void doSth1();
  11. descriptor: ()V
  12. flags: ACC_PUBLIC
  13. Code:
  14. stack=2, locals=3, args_size=1
  15. 0: ldc #5 // class com/hollis/SynchronizedTest
  16. 2: dup
  17. 3: astore_1
  18. 4: monitorenter
  19. 5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  20. 8: ldc #3 // String Hello World
  21. 10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  22. 13: aload_1
  23. 14: monitorexit
  24. 15: goto 23
  25. 18: astore_2
  26. 19: aload_1
  27. 20: monitorexit
  28. 21: aload_2
  29. 22: athrow
  30. 23: return

反编译后 我们可以看到Java编译器为我们生成的字节码
在对于doSth和doSth1的处理上稍有不同
也就是说 JVM对于同步方法和同步代码块的处理方式不同

对于**同步方法 JVM采用ACC_SYNCHRONIZED**标记符来实现同步
对于**同步代码块 JVM采用monitorenter、monitorexit**两个指令来实现同步

对于同步方法在JVM规范中的描述大致为:
方法级的同步是隐式的 同步方法的常量池中会有一个ACC_SYNCHRONIZED标志
当某个线程要访问某个方法的时候 会检查是否有ACC_SYNCHRONIZED 如果方法有设置 则需要先获得监视器锁
然后开始执行方法 方法执行之后再释放监视器锁
这时如果其他线程来请求执行方法 会因为无法获得监视器锁而被阻断住
值得注意的是 如果在方法执行过程中 发生了异常 并且方法内部并没有处理该异常 那么在异常被抛到方法外面之前监视器锁会被自动释放

对于monitorenter和monitorexit两个指令在JVM规范中的描述大致为:
可以把执行monitorenter指令理解为加锁 执行monitorexit理解为释放锁
每一个锁关联一个线程持有者和一个计数器,当计数器为0时表示该锁没有被任何线程持有
当一个线程获得锁后,该计数器自增变为 1
当同一个线程再次获得该对象的锁的时候,计数器再次自增(可重入锁)
当同一个线程释放锁的时候,计数器再自减,当计数器为0的时候,锁将被释放,其他线程便可以获得锁

总结
1)同步方法通过ACC_SYNCHRONIZED关键字隐式的对方法进行加锁
当线程要执行的方法被标注上ACC_SYNCHRONIZED时 需要先获得锁才能执行该方法
2)同步代码块通过monitorenter和monitorexit执行来进行加锁
当线程执行到monitorenter的时候要先获得所锁 才能执行后面的方法 当线程执行到monitorexit的时候则要释放锁
每个对象维护着一个记录着被锁次数的计数器 当计数器数字为0时表示可以被任意线程获得锁
当计数器不为0时 只有获得锁的线程才能再次获得锁 即可重入锁