JavaEE--锁策略+synchronized优化+CAS

本文详细介绍了乐观锁与悲观锁的区别,重量级锁与轻量级锁的原理,以及自旋锁、挂起等待锁和可重入锁的概念。重点讨论了synchronized的优化策略,包括锁升级、消除和粗化,以及CAS在原子类和自旋锁中的应用,以及如何解决ABA问题。
摘要由CSDN通过智能技术生成

1.常见的锁策略

1.乐观锁与悲观锁

乐观锁:预测当前锁冲突概率不大,后续要做的工作往往就很少,加锁开销就更小

悲观锁:预测当前锁冲突概率大,后续要做的工作往往就很多,加锁开销就更大

 

2.重量级锁与轻量级锁

重量级锁:加锁过程中做的事情多

轻量级锁:加锁过程中做的事情少

 

3.自旋锁与挂起等待锁

自旋锁:轻量级锁的一种典型实现方式,例如使用while循环检测锁是否被占用,没释放就继续执行循环(cpu空转),释放了就获取锁

挂起等待锁:重量级锁的一种典型实现方式,借助系统中的线程调度机制,当尝试加锁,并且锁被占用了,出现锁冲突,就会让当前这个尝试加锁的线程,被挂起(进入阻塞状态),此时该线程就不会参与调度了,直到锁被释放时,系统才能唤醒这个线程并且尝试重新获取锁

自旋锁拿到锁的速度更快(消耗了更多的cpu资源,但是一旦锁被释放,就能第一时间拿到锁),挂起等待锁拿到锁的速度更慢(更节省cpu,但一旦线程被阻塞了,什么时候被唤醒是不可控的,可能消耗很长时间)

 

4.可重入锁与不可重入锁

可重入锁:一个线程针对一把锁连续加锁两次不会死锁

不可重入锁:一个线程针对一把锁连续加锁两次会死锁

 

5.公平锁与非公平锁

公平锁:严格按照先来后到的顺序获取锁(哪个线程等待时间长,哪个线程就拿到锁)

非公平锁:随机获取到锁,和线程等待时间无关

 

6.互斥锁与读写锁

互斥锁:一个线程获取到锁后,另一个线程也尝试加这个锁,就会阻塞等待(发生锁冲突)
读写锁:给读操作和写操作分别加锁   (1)读锁和读锁之间,不会产生互斥 (2)写锁和写锁之间会产生互斥 (3)读锁和写锁之间会产生互斥

2.synchronized优化

2.1 synchronized的性质

基于上述的锁策略,我们来对synchronized锁的性质进行一个总结。

1) synchronized既是乐观锁也是悲观锁,它能够自动统计出当前的锁冲突的次数,判断当前锁冲突概率的高低。当冲突概率低的时候,就按乐观锁的方式来执行,当冲突概率高的时候,就会升级成悲观锁的方式来执行

2) synchronized即是轻量级锁也是重量级锁,随着锁冲突的进一步提升,可以由轻量级锁升级为重量级锁;synchronized轻量级锁部分,基于自旋锁实现;重量级锁部分,基于挂起等待锁实现

3) synchronized是不可重入锁

4) synchronized是非公平锁,当多个线程尝试获取这个锁时,按照概率均等的方式来获取

5) synchronized是互斥锁

2.2 锁升级

锁升级的过程:

偏向锁:首次对对象进行加锁时,不进行真正的加锁,而是做一个“标记”,如果没有别的线程尝试对这个对象加锁,就保持此状态直到解锁,从而降低程序开销,一旦真的涉及到其他的线程竞争,再取消偏向锁状态,进入轻量级锁状态

2.3 锁消除

锁消除是一种优化策略。当代码中写了加锁操作时,编译器与JVM会当前的代码是否真的需要进行加锁做出判定,如果不需要加锁,就会自动把加锁操作优化掉 (编译器优化需要保证优化后的逻辑和优化前等价)

2.4 锁粗化

锁粗化也是一种优化策略。当一段逻辑需要进行频繁加锁解锁时,编译器就会自动把多次细粒度的锁合并成一次粗粒度的锁。

锁的粒度:加锁的代码块内代码越多,锁的粒度越粗,反之,锁的粒度越细

3.CAS

3.1 CAS的简要介绍

CAS,全称为compare and swap,意为比较和交换,在进行比较后将内存地址和寄存器中的值进行交换,这个操作过程在整体上是一条cpu指令,CAS的流程可以想象成一个方法

   boolean cas(address,reg1,reg2){//address内存地址,reg1,reg2寄存器
        if(address == reg1){
            //交换address内存地址中的值和reg2寄存器中的值
       //一般更关心交换交换后内存地址中的值,所以可以大致认为是把reg2寄存器中的值赋给内存地址
            return true;
        }
        return false;
    }

3.2 CAS的应用

1) 原子类

Java中有一些类对CAS进行了进一步封装。典型的就是"原子类"。针对原子类,我们来介绍其中的一种,AtomicInteger类,对int进行封装,保证++,--,+=等操作是原子的。

import java.util.concurrent.atomic.AtomicInteger;

public class atomic {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();//等价于count++
                //count.incrementAndGet();等价于++count
                //count.getAndDecrement();等价于count--
                //count.decrementAndGet();等价于--count
                //count.getAndAdd(10);等价于count+=10
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count=" + count);
    }
}

运行结果如下:

可以看到在没有加锁的情况下,结果仍然是正确的。基于CAS,原子类没有通过加锁的方式来实现线程安全代码,这种方式称为“无锁编程”,但只适用于一些特殊场景。

2) 自旋锁 

public class SpinLock {
    private Thread owner=null;//持有锁的线程,未加锁状态时,owner为null
    public void lock() {
        //通过 CAS 看当前锁是否被某个线程持有.
        //如果owner的值为空,就把调用lock方法的线程赋值给owner,CAS返回true,取反为false,循环结束
        //如果owner的值不为空,CAS不会进行交换,返回false,取反为true,继续循环进行判定
        while (!CAS(this.owner, null, Thread.currentThread())) {
        }
    }
    public void unlock(){
        this.owner=null;
    }
}

 3.3 ABA问题

使用CAS编写代码时,其比较操作需要检查当前内存的值是否被其他线程修改,如果被修改就要稍后重新尝试,如果没被修改,接下来就可以直接修改,但当前内存的值不变不代表当前内存的值没有改变过,另一个线程可能把这个值从A修改为B再重新修改为A,在这种极端情况下,可能会产生bug,我们通过一个存款取款的例子来理解一下

当只有t1和t2线程进行操作时,不会产生问题,但假设有一个线程t3在t2线程执行完之后存款500,使banlance变为1000,导致t1线程执行if语句时balance与oldbanlance的值相等,使CAS比较成立并且又扣款500,最后导致取了500,余额却扣款了1000。这个过程就属于ABA问题产生的bug。

那么如何上述ABA问题呢?可以通过引入版本号,在每次操作余额时,版本号++,同时通过CAS判断版本号,这样就能通过版本号是否改变来判断数据是否改变,从而有效避免ABA问题。

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaEE-图书管理系统通常需要包含以下数据库表: 1. 用户表(user):包括用户ID、用户名、密码、邮箱等字段。 2. 图书表(book):包括图书ID、图书名称、作者、出版社、ISBN号等字段。 3. 借阅记录表(borrow_record):包括借阅记录ID、用户ID、图书ID、借阅时间、归还时间等字段。 4. 管理员表(admin):包括管理员ID、管理员名称、密码等字段。 以下是JavaEE-图书管理系统数据库代码示例: ```sql -- 用户表 CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(50) NOT NULL, `email` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 图书表 CREATE TABLE `book` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `author` varchar(50) NOT NULL, `publisher` varchar(50) NOT NULL, `isbn` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 借阅记录表 CREATE TABLE `borrow_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `book_id` int(11) NOT NULL, `borrow_time` datetime NOT NULL, `return_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `fk_user_id_idx` (`user_id`), KEY `fk_book_id_idx` (`book_id`), CONSTRAINT `fk_book_id` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 管理员表 CREATE TABLE `admin` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` -- 相关问题--: 1. 如何创建数据库表? 2. 如何在JavaEE中连接数据库? 3. 数据库中的什么是外键?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值