关于java:Synchronized原理和锁升级

42次阅读

共计 3166 个字符,预计需要花费 8 分钟才能阅读完成。

Synchronized 应用场景

  • Synchronized 润饰实例办法:为以后实例 this 加锁
  • Synchronized 润饰静态方法:为以后 Class 实例加锁
  • Synchronized 润饰代码块:为 Synchronized 前面括号里润饰的实例加锁

留神:

  • 同一个类的不同实例领有不同的锁,因而不会互相阻塞。
  • 应用 Synchronized 润饰 Class 和实例时,因为 Class 和实例别离领有不同的锁,因而不会互相阻塞。
  • 如果一个线程正在拜访实例的一个 Synchronized 润饰的实例办法时,其它线程不仅不能拜访该 Synchronized 润饰的实例办法,该实例的其它 ynchronized 润饰的实例办法也不能拜访,因为一个实例只有一个监视器锁,然而其它线程能够拜访该实例的无 Synchronized 润饰的实例办法或 Synchronized 润饰的静态方法。

Synchronized 如何保障线程平安

1.Synchronized 保障原子性
Synchronized 保障只有一个线程能拿到锁,进入同步代码块

2.synchronized 保障可见性
执行 synchronized 时,对应的 lock 原子操作会让工作内存中从主内存中更新共享变量的值

3.synchronized 保障有序性
synchronized 后,尽管进行了重排序,保障只有一个线程会进入同步代码块,也能保障有序性。

Synchronized 个性

可重入

public class SynchronizedDemo {private static Object obj = new Object();

    public static void main(String[] args) {Runnable sellTicket = new Runnable() {
            @Override
            public void run() {synchronized (SynchronizedDemo.class) {System.out.println("我是 run");
                    test01();}
            }
            public void test01() {synchronized (SynchronizedDemo.class) {System.out.println("我是 test01"); }
            } };
        new Thread(sellTicket).start();}

}

我是 run
我是 test01

synchronized 是可重入锁,外部锁对象中会有一个计数器记录线程获取几次锁,在执行完同步代码块时,计数器的数量会 -1,晓得计数器的数量为 0,就开释这个锁。

不可中断

public class SynchronizedDemo {private static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {Runnable run = () -> {synchronized (obj) {String name = Thread.currentThread().getName();
                System.out.println(name + "进入同步代码块");
                // 保障不退出同步代码块
                try {Thread.sleep(888888);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        };

        // 3. 先开启一个线程来执行同步代码块
        Thread t1 = new Thread(run);
        t1.start();

        Thread.sleep(1000);

        // 4. 后开启一个线程来执行同步代码块(阻塞状态)
        Thread t2 = new Thread(run);
        t2.start();
        // 5. 进行第二个线程
        System.out.println("进行线程前");
        t2.interrupt();
        System.out.println("进行线程后");
        System.out.println(t1.getState());
        System.out.println(t2.getState());
    }
}

Thread- 0 进入同步代码块
进行线程前
进行线程后
TIMED_WAITING
BLOCKED

能够看到 t2.interrupt()之后,t2 线程依然是阻塞状态,不可被中断
不可中断是指,当一个线程取得锁后,另一个线程始终处于阻塞或期待状态,前一个线程不开释锁,后一个线程会始终阻塞或期待,不可被中断。
synchronized 属于不可被中断
Lock 的 lock 办法是不可中断的
Lock 的 tryLock 办法是可中断的

Synchronized 原理

public class SynchronizedDemo {
    private int i;
    public void sync() {synchronized (this) {i++;}
    }
}

javap 命令对 class 文件进行反汇编,查看字节码指令如下:

能够发现 synchronized 同步代码块是通过加 monitorenter 和 monitorexit 指令实现的。
每个对象都有个监视器锁(monitor),当 monitor 被占用的时候就代表对象处于锁定状态,而 monitorenter 指令的作用就是获取 monitor 的所有权,monitorexit 的作用是开释 monitor 的所有权

public class SynchronizedDemo {public synchronized void sync() {}}

javap 命令对 class 文件进行反汇编,查看字节码指令如下:

当办法调用时,调用指令将会查看办法的 ACC_SYNCHRONIZED 拜访标记是否被设置,如果设置了,执行线程将先获取 monitor,获取胜利之后能力执行办法体,办法执行完后再开释 monitor。在办法执行期间,其余任何线程都无奈再取得同一个 monitor 对象。
两种同步形式实质上没有区别,只是办法的同步是一种隐式的形式来实现。

Synchronized 锁降级

synchronized 的锁降级,说白了,就是当 JVM 检测到不同的竞争情况时,会主动切换到适宜的锁实现,这种切换就是锁的降级。
synchronized 是乐观锁,在操作同步资源之前须要给同步资源先加锁,这把锁就是存在 Java 对象头里的。失去锁的线程能拜访同步资源。

Java 对象头中的 MarkWord

Mark Word:默认存储对象的 HashCode,分代年龄和锁标记位信息。这些信息都是与对象本身定义无关的数据,所以 Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会依据对象的状态复用本人的存储空间,也就是说在运行期间 Mark Word 里存储的数据会随着锁标记位的变动而变动。

无锁

CAS

偏差锁

一段同步代码始终被一个线程所拜访,那么该线程会主动获取锁,升高获取锁的代价。
偏差锁的偏是指会偏差第一个取得锁的线程。
当一个线程拜访同步代码块并获取锁时,会通过 CAS 操作在 Mark Word 里存储锁偏差的线程 ID。在线程进入和退出同步块时不再通过 CAS 操作来加锁和解锁,而是检测 Mark Word 里是否存储着指向以后线程的偏差锁。
引入偏差锁是为了在无多线程竞争的状况下尽量减少不必要的锁执行操作。

轻量级锁

是指当锁是偏差锁的时候,被另外的线程所拜访,偏差锁就会降级为轻量级锁,
将锁对象的 MarkWord 复制到以后线程的栈帧中的 LockRecod 中,CAS 操作尝试将对象的 MarkWord 更新为指向 LockRecord 的指针,如果这个更新动作胜利了,那么这个线程就领有了该对象的锁。
在多线程交替执行同步块的状况下,也是没有线程竞争的状况下,用 CAS 就能解决了,能够防止重量级锁引起的性能耗费

重量级锁

monitor 锁

Sychronized 与 Lock 的区别

参考:
不可不说的 Java“锁”事

正文完
 0