锁的分类和synchronized底层原理

文章目录

  • 一、JAVA中锁的概念
    • 1、自旋锁
    • 2、乐观锁
    • 3、悲观锁
    • 4、独享锁(写)
    • 5、共享锁(读)
      • ReadWriteLock
    • 6、可重入锁、不可重入锁
    • 7、公平锁、非公平锁
  • 二、synchronized的底层原理
    • 1、堆内存中的Java对象
    • 2、轻量级锁
    • 3、重量级锁
    • 4、偏向锁
    • 5、锁的升级过程
    • 6、重量级锁 - 监视器(monitor)

一、JAVA中锁的概念

1、自旋锁

是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

自旋锁有两种表现形式:
1、AtomicIneger 中的使用场景,自旋是为了修改一个变量,直接用CAS去进行操作
2、如下形式,通过自旋实现了一把锁

public class Demo2_SpinLock {
    private AtomicReference owner = new AtomicReference();
    public void lock() {
        Thread current = Thread.currentThread();
        // 利用CAS
        while (!owner.compareAndSet(null, current)) {
            // DO nothing
        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        owner.compareAndSet(current, null);
    }


    public static void main(String args[]){
        Demo2_SpinLock lock = new Demo2_SpinLock();
        lock.lock();
        //do something
        lock.unlock();
    }
}

2、乐观锁

假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改。

3、悲观锁

假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。synchronized就是典型的悲观锁。

4、独享锁(写)

给资源加上写锁,线程可以修改资源,其他线程不能再加锁; (只能单个线程写)

5、共享锁(读)

给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁; (可多个线程读)

应用场景:给数据库连接加说,限制数据库连接数量,同一时间只能有指定数量的连接,这个锁就是共享锁。

ReadWriteLock

概念: ReadWriteLock维护了一对关联锁,一个只用于读操作,一个只用于写操作;其中读锁可以由多个线程同时持有,而写锁是拍他的。另外同一时间,两把锁不能被不同线程持有
使用场景: 适合读取操作多于写入操作的场景,改进互斥锁的性能。比如:集合的并发线程安全性改造、缓存组件。
锁降级: 指的是写锁降级成为读锁。持有写锁的同时,再次获取读锁,随后释放写锁的过程。写锁是线程独占,读锁是共享,所以写->读是降级。(读->写,是不能实现的)

6、可重入锁、不可重入锁

线程拿到一把锁之后,该线程可以多次自由进入同一把锁所同步的其他代码就称可重入锁;获取锁之后,即使是该线程也不能进入同一把锁的其他代码就称为不可重入锁。

可重入锁和补可重入锁都是针对同一线程获取到锁的情况下讨论的,即使是可重入锁也不能让多个线程同时获取到锁,如果多个线程可获取到那么就是共享锁了。

使用可重入锁时,锁了多少次锁,就要释放多少次才能把锁真正的释放掉。

package com.dongnao.concurrent.period5;

import com.dongnao.concurrent.period4.KodyLock;

import java.util.concurrent.locks.ReentrantLock;

public class Demo1_ReentrantTest {

    private static  int i = 0;

    //可重入锁
    private final static ReentrantLock lc = new ReentrantLock();
    //    private final static KodyLock lc = new KodyLock();
    public static void add() throws InterruptedException {
        lc.lock();
        i++;
        System.out.println("here i am...");
        Thread.sleep(1000L);
        add();
        lc.unlock();
    }
    public static void main(String args[]) throws InterruptedException {
        add();
    }
}

上面代码中ReentrantLock就是一个可重入锁,程序会一直输出here i am... ,如果ReentrantLock是不可重入锁,那么只会输出一次here i am...,第二次调用add方法的时候就会阻塞住。1
输出结果:

here i am...
here i am...
here i am...
here i am...
here i am...
here i am...
here i am...
。。。

补充:synchronized是可重入锁。

可重入锁特性的实现需要解决以下两个问题。
1)线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
2)锁的最终释放。
nonfairTryAcquire方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。同步状态表示锁被一个线程重复获取的次数。
如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同
步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。

7、公平锁、非公平锁

争抢锁的顺序,如果是按先来后到,则为公平,否则就是非公平锁。

二、synchronized的底层原理

1、堆内存中的Java对象

每个对象在内存中存储的除了基本的字段属性值(如果字段是复杂对象的话,那么存储的就是对象引用),还有一个对象头。

对象头里面的Mark Word记录这对象锁的状态,有如下四种状态:

2、轻量级锁

在未锁定的状态下,可以通过CAS来抢锁,抢到的是轻量级锁

3、重量级锁

轻量级锁中的自旋有一定的次数限制,超过了次数限制,轻量级锁升级为重量级锁。

4、偏向锁

在JDK6 以后,默认已经开启了偏向锁这个优化,通过JVM 参数 -XX:-UseBiasedLocking 来禁用偏向锁,若偏向锁开启,只有一个线程抢锁,可获取到偏向锁。

5、锁的升级过程


1、处于Runnable状态而还没运行的线程1,会去抢owner,抢到之后开启偏向锁,线程运行。
2、此时如果有线程2来抢锁,那么锁会升级成轻量级锁
3、如果线程2抢不到锁(线程1还没有释放锁),那么线程2会自旋继续抢锁,自旋有一定的限制,自旋超过一定的次数,锁再次升级为重量级锁,抢不到锁的线程会进入entyList集合中等待执行(在重量级配图中所示)。
4、如果线程2,抢到了锁(在线程2开始之前,线程1已经释放了锁),那么锁还是偏向锁的状态。
5、如果运行中的线程调用了wait等方法,那么线程就会进入WaitSet,处于Waiting状态。
6、如果有线程调用了notify方法,那么WaitSet(WaitSet中有线程的情况下)中的一个线程就会进入到EntryList中,参与或等待抢锁。如果调用的是notifyAll,那么WaitSet(WaitSet中有线程的情况下)中的所有线程都会进入到EntryList中,参与或等待抢锁。
注意:
1、wait、notify、notifyAll只能在synchronized关键字中使用,且调用wait、notify、notifyAll的对象与锁对象相同,否则会抛出IllegalMonitorStateException异常。
2、wait() 方法调用后,会破坏原子性。
补充:
1、偏向标记第一次有用,出现过争用后就没用了。 -XX:-UseBiasedLocking 禁用使用偏置锁定;
2、偏向锁,本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步(jvm为了少干活:同步在JVM底层是有很多操作来实现的,如果是没有争用,就不需要去做同步操作)

6、重量级锁 - 监视器(monitor)

修改mark word如果失败,会自旋CAS一定次数,该次数可以通过参数配置:超过次数,仍未抢到锁,则锁升级为重量级锁,进入阻塞。

monitor也叫做管程,计算机操作系统原理中有提及类似概念。一个对象会有一个对应的monitor。

你可能感兴趣的:(Java线程)