一、synchronized保证三大特性

synchronized保证原子性的原理
对num++;增加同步代码块后,保证同一时间只有一个线程操作num++;。就不会出现安全问题。
synchronized保证可见性的原理
synchronized保证可见性的原理,执行synchronized时,会对应lock原子操作会刷新工作内存中共享变 量的值。
synchronized保证有序性的原理
我们加synchronized后,依然会发生重排序,只不过我们有同步 代码块,可以保证只有一个线程执行同步代码中的代码。保证有序性。

二、synchronized的特性

可重入特性

意思就是一个线程可以多次执行synchronized,重复获取同一把锁。

  1. /*
  2. 目标:演示synchronized可重入
  3. 1.自定义一个线程类
  4. 2.在线程类的run方法中使用嵌套的同步代码块
  5. 3.使用两个线程来执行
  6. */
  7. public class Demo01 {
  8. public static void main(String[] args) {
  9. new MyThread().start();
  10. new MyThread().start();
  11. }
  12. public static void test01() {
  13. synchronized (MyThread.class) {
  14. String name = Thread.currentThread().getName();
  15. System.out.println(name + "进入了同步代码块2");
  16. }
  17. }
  18. }
  19. // 1.自定义一个线程类
  20. class MyThread extends Thread {
  21. @Override
  22. public void run() {
  23. synchronized (MyThread.class) {
  24. System.out.println(getName() + "进入了同步代码块1");
  25. Demo01.test01();
  26. }
  27. }
  28. }

可重入原理
synchronized的锁对象中有一个计数器(recursions变量)会记录线程获得几次锁.。在执行完同步代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁。可重入的好处

  1. 可以避免死锁
  2. 可以让我们更好的来封装代码

    不可中断特性

    什么是不可中断
    一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或等待状态,如果第一个线程不释放锁,第 二个线程会一直阻塞或等待,不可被中断。

    synchronized不可中断演示

    1. public class Test {
    2. private static Object obj = new Object();
    3. public static void main(String[] args) throws InterruptedException {
    4. // 1.定义一个Runnable
    5. Runnable run = () -> {
    6. // 2.在Runnable定义同步代码块
    7. synchronized (obj) {
    8. String name = Thread.currentThread().getName();
    9. System.out.println(name + "进入同步代码块");
    10. // 保证不退出同步代码块
    11. try {
    12. Thread.sleep(888888);
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. }
    17. };
    18. // 3.先开启一个线程来执行同步代码块
    19. Thread t1 = new Thread(run);
    20. t1.start();
    21. Thread.sleep(1000);
    22. // 4.后开启一个线程来执行同步代码块(阻塞状态)
    23. Thread t2 = new Thread(run);
    24. t2.start();
    25. // 5.停止第二个线程
    26. System.out.println("停止线程前");
    27. t2.interrupt();
    28. System.out.println("停止线程后");
    29. System.out.println(t1.getState());
    30. System.out.println(t2.getState());
    31. }
    32. }

    输出结果:Thread-0进入同步代码块 停止线程前 停止线程后 TIMED_WAITING BLOCKED

ReentrantLock可中断演示

  1. public class Test {
  2. private static Lock lock = new ReentrantLock();
  3. public static void main(String[] args) throws InterruptedException {
  4. // test01();
  5. test02();
  6. }
  7. // 演示Lock可中断
  8. public static void test02() throws InterruptedException {
  9. Runnable run = () -> {
  10. String name = Thread.currentThread().getName();
  11. boolean b = false;
  12. try {
  13. b = lock.tryLock(3, TimeUnit.SECONDS);
  14. if (b) {
  15. System.out.println(name + "获得锁,进入锁执行");
  16. Thread.sleep(88888);
  17. } else {
  18. System.out.println(name + "在指定时间没有得到锁做其他操作");
  19. }
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. } finally {
  23. if (b) {
  24. lock.unlock();
  25. System.out.println(name + "释放锁");
  26. }
  27. }
  28. };
  29. Thread t1 = new Thread(run);
  30. t1.start();
  31. Thread.sleep(1000);
  32. Thread t2 = new Thread(run);
  33. t2.start();
  34. System.out.println("停止t2线程前");
  35. t2.interrupt();
  36. System.out.println("停止t2线程后");
  37. Thread.sleep(4000);
  38. System.out.println(t1.getState());
  39. System.out.println(t2.getState());
  40. }
  41. // 演示Lock不可中断
  42. public static void test01() throws InterruptedException {
  43. Runnable run = () -> {
  44. String name = Thread.currentThread().getName();
  45. try {
  46. lock.lock();
  47. System.out.println(name + "获得锁,进入锁执行");
  48. Thread.sleep(88888);
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. } finally {
  52. lock.unlock();
  53. System.out.println(name + "释放锁");
  54. }
  55. };
  56. Thread t1 = new Thread(run);
  57. t1.start();
  58. Thread.sleep(1000);
  59. Thread t2 = new Thread(run);
  60. t2.start();
  61. System.out.println("停止t2线程前");
  62. t2.interrupt();
  63. System.out.println("停止t2线程后");
  64. Thread.sleep(1000);
  65. System.out.println(t1.getState());
  66. System.out.println(t2.getState());
  67. }
  68. }

控制台输出:Thread-0获得锁,进入锁执行 停止t2线程前 停止t2线程后 java.lang.InterruptedException atjava.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireNanos(AbstractQueuedSynchronizer.java:1245) at java.util.concurrent.locks.ReentrantLock.tryLock(ReentrantLock.java:442) at Test.lambda$test02$0(Test.java:24) at java.lang.Thread.run(Thread.java:748) TIMED_WAITING TERMINATED

三、synchronized简单原理

  1. public class SyncTest {
  2. public void syncBlock(){
  3. synchronized (this){
  4. System.out.println("hello block");
  5. }
  6. }
  7. public synchronized void syncMethod(){
  8. System.out.println("hello method");
  9. }
  10. }

使用javap对其进行反汇编,部分信息如下

  1. {
  2. public void syncBlock();
  3. descriptor: ()V
  4. flags: ACC_PUBLIC
  5. Code:
  6. stack=2, locals=3, args_size=1
  7. 0: aload_0
  8. 1: dup
  9. 2: astore_1
  10. 3: monitorenter // monitorenter指令进入同步块
  11. 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  12. 7: ldc #3 // String hello block
  13. 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  14. 12: aload_1
  15. 13: monitorexit // monitorexit指令退出同步块
  16. 14: goto 22
  17. 17: astore_2
  18. 18: aload_1
  19. 19: monitorexit // monitorexit指令退出同步块
  20. 20: aload_2
  21. 21: athrow
  22. 22: return
  23. Exception table:
  24. from to target type
  25. 4 14 17 any
  26. 17 20 17 any
  27. public synchronized void syncMethod();
  28. descriptor: ()V
  29. flags: ACC_PUBLIC, ACC_SYNCHRONIZED //添加了ACC_SYNCHRONIZED标记
  30. Code:
  31. stack=2, locals=1, args_size=1
  32. 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  33. 3: ldc #5 // String hello method
  34. 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  35. 8: return
  36. }

synchronized修饰代码块时

monitorenter

每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获 取该monitor。 当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应 的monitor的所有权。其过程如下:

  1. 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为 monitor的owner(所有者)
  2. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
  3. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直 到monitor的进入数变为0,才能重新尝试获取monitor的所有权。

monitorenter小结: synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到这个 同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量owner:拥有 这把锁的线程,recursions会记录线程拥有锁的次数,当一个线程拥有monitor后其他线程只能等待

monitorexit

1.能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。

  1. 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出 monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个 monitor的所有权

monitorexit释放锁。 monitorexit插入在方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit。
总结:synchronized在修饰代码块时,是通过monitorenter 和 monitorexit来保证并发安全。

synchronized 修饰方法的的情况

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
不过两者的本质都是对对象监视器 monitor 的获取。

四、Lock Record

字面意思就是锁记录。通过对Java对象头的介绍可以看到锁信息也是存在于对象的mark word中的。当对象状态为偏向锁(biasable)时,mark word存储的是偏向的线程ID;当状态为轻量级锁(lightweight locked)时,mark word存储的是指向线程栈中Lock Record的指针;当状态为重量级锁(inflated)时,为指向堆中的monitor对象的指针。

Lock Record的结构

线程在执行同步块之前,JVM会先在当前的线程的栈帧中创建一个Lock Record,其包括一个用于存储对象头中的 mark word(官方称之为Displaced Mark Word)以及一个指向对象的指针。下图右边的部分就是一个Lock Record。
image.png