Java基础系列:锁

1 锁

Java中锁的种类繁多,如下表所示。

序号描述锁实现
1自旋锁当前线程获取锁时,如果该锁被其他线程占用,当前线程循环等待,并不断判断是否成功获取锁,直到获取锁退出循环TicketLock,CLHLock,MCSLock
2阻塞锁
3可重入锁锁的重用,当前线程使用可重入锁后,在该线程中调用了其他可重入锁的线程,无需重新申请锁,直接在当前锁中执行即可synchronized,ReentrantLock
4读写锁读写分离,资源可以被多个线程同时读,或者被一个线程写,不能多个线程同时读和写ReadWriteLock
5互斥锁
6悲观锁每次操作前都会加锁,因为,每次操作时都会认为会有其他线程会修改数据表锁,行锁,读锁,写锁
7乐观锁每次操作不会加锁,因为,每次操作时都认为其他线程不会修改数据
8公平锁以请求顺序分配锁,等待时间最长的线程最优先获得锁
9可中断锁可中断的锁,线程A执行锁中的任务,另一线程B等待获取该锁,当线程B不想等待时,可以中断等待,执行其他任务Lock
10偏向锁偏向于第一个访问锁的线程,如果该锁没有被其他线程访问,则持有偏向锁的线程永远无需触发同步

2 偏向锁

偏向锁即该锁偏向于第一个访问锁的线程,如果该锁没有被其他线程访问,则持有偏向锁的线程永远无需触发同步。消除CAS,降低Cache一致性流量。
大多数情况下锁不仅不存在多线程竞争,而且总由同一个线程多次获得(Hotspot作者研究总结),为了让线程获得锁的代价更低,引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头(Mark Word)和线程栈帧中的所记录中存储锁偏向的线程ID(初始CAS),以后该线程在进入和退出同步块时无需花费CAS加锁和解锁。

  • Mark word组成
序号锁状态线程ID对象分代年龄偏向锁标志锁标志位
1无锁/分代年龄001
2偏向锁线程ID分代年龄101
3轻量级锁指向栈中锁记录的指针//00
4重量级锁指向互斥量(重量级锁)的指针//10
5GC标记///11

2.1 偏向锁持有

JVM偏向锁-XX:+UseBiasedLocking默认开启,确认instanace可用,偏向锁可用,即Mark word偏向锁标志位1。
当前线程持有的对象判断对象头的Mark Word中是否存储指向当前线程的偏向锁,如果存在,表示线程获得偏向锁,如果不存在,则测试偏向锁的标识是否设置为1,如果没有设置,使用CAS竞争锁,竞争之后,偏向锁转为轻量级锁,如果设置,尝试使用CAS将对象头的偏向锁指向当前线程。

  • 验证对象bias位
    如果为0,该对象不可偏向,应该使用轻量级锁算法。
  • 验证对象所属InstanceKlass prototype的biase位
    确认bias是否被设置,如果没有设置,该类所有对象全部不允许被偏向锁锁定,并且该类所有对象的bias位都需要被重置,使用轻量级锁替换。
  • 校验epoch位
    校验对象的Mark Word的epoch位是否与该对象所属的InstanceKlass prototype的Mark Word epoch匹配,如果不匹配,表明偏向已经过期,需要重新偏向,偏向线程可以简单使用CAS重新偏向这个锁对象。
  • 校验owner线程
    比较偏向线程ID与当前线程ID,如果匹配,表明当前线程已经获取偏向,可以安全返回,如果不匹配,对象锁被假定为匿名偏向状态,当前线程应该尝试使用CAS获得偏向,如果失败,尝试撤销,回退到轻量级锁,如果获得偏向成功,直接返回。

2.2 偏向锁撤销

偏向锁使用了一种等到竞争出现才释放锁的机制。当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁撤销需要等待全局安全点,首先暂停拥有偏向锁的线程,检查持有偏向锁的线程是否存活,如果线程非活动状态,对象头设置为无锁状态。如果线程仍然存活,恢复到无锁,最后唤醒暂停的线程。

3 可重入锁

自动获取当前锁。线程级别的锁,当线程A执行的方法M使用synchronized关键字修饰,同时方法M内的方法N也使用synchronized修饰,此时,线程A执行方法N时,无需重新申请锁,直接重用线程A已经申请的锁即可,避免了死锁。

3.1 加synchronized

3.1.1 测试

package lock;

import java.util.logging.Logger;

/**
 * Synchronized测试.
 *
 * @author xindaqi
 * @since 2021/4/13 17:01
 */
public class SynchronizedTest implements Runnable {

    private static final Logger logger = Logger.getLogger("SynchronizedTest");

    @Override
    public void run() {
        try {
            add();
        } catch(InterruptedException e) {
            Thread.currentThread().interrupt();
        }

    }
    public synchronized void add() throws InterruptedException {
        logger.info("执行add()方法的Thread:"  + Thread.currentThread().getName());
        logger.info("开始执行add()方法");
        Thread.sleep(1000);
        logger.info("开始执行delete()方法");
        delete();
        logger.info("完成add()方法执行");
    }

    public synchronized void delete() throws InterruptedException {
        logger.info("执行delete()方法的Thread:"  + Thread.currentThread().getName());
        logger.info("开始执行delete()方法");
        Thread.sleep(1000);
        logger.info("完成delete()方法执行");
    }

    public static void main(String[] args) {
        try {

            SynchronizedTest synchronizedTest = new SynchronizedTest();
            Thread thread1 = new Thread(synchronizedTest, "thread1");
            Thread thread2 = new Thread(synchronizedTest, "thread2");
            thread1.start();
            thread2.start();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3.1.2 结果

四月 13, 2021 5:54:27 下午 lock.SynchronizedTest add
信息: 执行add()方法的Thread:thread1
四月 13, 2021 5:54:27 下午 lock.SynchronizedTest add
信息: 开始执行add()方法
四月 13, 2021 5:54:28 下午 lock.SynchronizedTest add
信息: 开始执行delete()方法
四月 13, 2021 5:54:28 下午 lock.SynchronizedTest delete
信息: 执行delete()方法的Thread:thread1
四月 13, 2021 5:54:28 下午 lock.SynchronizedTest delete
信息: 开始执行delete()方法
四月 13, 2021 5:54:29 下午 lock.SynchronizedTest delete
信息: 完成delete()方法执行
四月 13, 2021 5:54:29 下午 lock.SynchronizedTest add
信息: 完成add()方法执行
四月 13, 2021 5:54:29 下午 lock.SynchronizedTest add
信息: 执行add()方法的Thread:thread2
四月 13, 2021 5:54:29 下午 lock.SynchronizedTest add
信息: 开始执行add()方法
四月 13, 2021 5:54:30 下午 lock.SynchronizedTest add
信息: 开始执行delete()方法
四月 13, 2021 5:54:30 下午 lock.SynchronizedTest delete
信息: 执行delete()方法的Thread:thread2
四月 13, 2021 5:54:30 下午 lock.SynchronizedTest delete
信息: 开始执行delete()方法
四月 13, 2021 5:54:31 下午 lock.SynchronizedTest delete
信息: 完成delete()方法执行
四月 13, 2021 5:54:31 下午 lock.SynchronizedTest add
信息: 完成add()方法执行

Process finished with exit code 0

3.1.3 解析

由运行结果可知,方法add和delete多线程调用时,是串行执行,当线程1执行结束后,线程2才启动执行。当add方法中调用delete方法时,仍在当前线程中,没有重新申请锁。
synchronized多线程安全,且是可重入类型的锁。

3.2 未加synchronized

3.2.1 测试

package lock;

import java.util.logging.Logger;

/**
 * 非synchronized测试.
 *
 * @author xindaqi
 * @since 2021/4/13 17:49
 */
public class WithoutSynchronizedTest implements Runnable {

    private static final Logger logger = Logger.getLogger("SynchronizedTest");

    @Override
    public void run() {
        try {
            add();
        } catch(InterruptedException e) {
            Thread.currentThread().interrupt();
        }

    }
    public void add() throws InterruptedException {
        logger.info("执行add()方法的Thread:"  + Thread.currentThread().getName());
        logger.info("开始执行add()方法");
        Thread.sleep(1000);
        logger.info("开始执行delete()方法");
        delete();
        logger.info("完成add()方法执行");
    }

    public void delete() throws InterruptedException {
        logger.info("执行delete()方法的Thread:"  + Thread.currentThread().getName());
        logger.info("开始执行delete()方法");
        Thread.sleep(1000);
        logger.info("完成delete()方法执行");
    }

    public static void main(String[] args) {
        try {

            WithoutSynchronizedTest withoutSynchronizedTest = new WithoutSynchronizedTest();
            Thread thread1 = new Thread(withoutSynchronizedTest, "thread1");
            Thread thread2 = new Thread(withoutSynchronizedTest, "thread2");
            thread1.start();
            thread2.start();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3.2.2 结果

四月 13, 2021 5:53:05 下午 lock.WithoutSynchronizedTest add
信息: 执行add()方法的Thread:thread2
四月 13, 2021 5:53:05 下午 lock.WithoutSynchronizedTest add
信息: 执行add()方法的Thread:thread1
四月 13, 2021 5:53:05 下午 lock.WithoutSynchronizedTest add
信息: 开始执行add()方法
四月 13, 2021 5:53:05 下午 lock.WithoutSynchronizedTest add
信息: 开始执行add()方法
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest add
信息: 开始执行delete()方法
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest add
信息: 开始执行delete()方法
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest delete
信息: 执行delete()方法的Thread:thread1
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest delete
信息: 执行delete()方法的Thread:thread2
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest delete
信息: 开始执行delete()方法
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest delete
信息: 开始执行delete()方法
四月 13, 2021 5:53:07 下午 lock.WithoutSynchronizedTest delete
信息: 完成delete()方法执行
四月 13, 2021 5:53:07 下午 lock.WithoutSynchronizedTest add
信息: 完成add()方法执行
四月 13, 2021 5:53:07 下午 lock.WithoutSynchronizedTest delete
信息: 完成delete()方法执行
四月 13, 2021 5:53:07 下午 lock.WithoutSynchronizedTest add
信息: 完成add()方法执行

Process finished with exit code 0

2.2.3 解析

由运行结果可知,方法add和delete多线程调用时,是并行执行,线程1和线程2同时执行。
当不使用锁时,非线程安全。


【参考文献】
[1]https://blog.csdn.net/zengdeqing2012/article/details/103894531
[2]https://www.cnblogs.com/qifengshi/p/6831055.html
[3]https://www.jianshu.com/p/9d3660ad4358?utm_source=oschina-app
[4]https://baijiahao.baidu.com/s?id=1662834023898525632&wfr=spider&for=pc
[5]http://blog.sina.com.cn/s/blog_c038e9930102v2hs.html
[6]https://blog.csdn.net/qq_24434251/article/details/114435631
[7]https://blog.csdn.net/weixin_42213903/article/details/97044043
[8]https://blog.csdn.net/zzti_erlie/article/details/103997713

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值