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_WAITINGBLOCKED

能够看到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“锁”事