并发锁的学习

本文介绍了并发编程中的锁机制,包括锁的定义、AQS、事务隔离级别、CAS操作及其ABA问题。讨论了乐观锁与悲观锁的优缺点,以及读锁和写锁的区别。还涉及到了行锁、表锁、公平锁与非公平锁、可重入锁与不可重入锁的概念,以及自旋锁的原理。最后提到了数据库中的MVCC机制和解决并发问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

锁的定义

锁是用来协调多个线程并发访问同一共享资源时带来的安全问题,频繁用锁必然会带来性能问题,但不用锁又会造成安全问题

1)从性能上分:乐观锁和悲观锁

  • 乐观锁:CAS自旋锁,是非常经典的乐观锁,并发性能比较好,但是自旋会造成很大的开销,悲观的认为当前环境下并发情况不是很很严重,任务提交时才才判断是否冲突

  • 悲观锁:悲观的认为当前环境下并发情况是很严重的,所有的任务在执行时都要加上锁,保证了安全型,失去了并发性,并发性能差

    乐观锁:无锁,在数据提交时才去判断是否发生冲突,version

    悲观锁:每次访问数据时,都需要加锁

2)从数据库操作的类型上划分

读锁和写锁,其都是悲观锁

  • 读锁(共享锁):对读取同一行数据的并发来说,是可以同时进行的,但是写不行

  • 写锁(排它锁):在加写锁之后释放锁之前,其他的所有操作都不能进行

3)从数据的操作粒度上来划分

表锁和行锁

  • 表锁:对整张表进行上锁

    • mysiam默认支持表锁,多个线程并发操作时,一个线程操作myisam后其他所有操作都不能进行
    • innodb上表锁:lock table 表1 read/write/,表2 read /write
    • show open tables 查看当前会话的所有锁
    • unlock tables 释放当前会话的所有锁
  • 行锁:对表中的某一行记录进行上锁,支持并发读其是一个读锁

    • innodb支持行锁,在并发事务里,每个事务的读写删相当于上行锁

    • 上锁的开销大,加锁速度慢但是并发性更好

    • update tb_author set name=‘m’ where id=1

    • select * from tb_author where id=1 for update

AQS

AQS的理解

抽象队列同步器,其是并发包中的基础组件,用来实现各种锁,ReentrantLock底层由其实现包括 state变量,等待队列,加锁线程三个核心内容

实现原理:初始时state=0,加锁线程为null,线程1加锁后利用CAS判断state递增是否成功若成功加锁队列由null变为线程1,如果此时线程2也想加锁,但是state已经被使用则其进入等待队列之中,若线程1释放锁(state递减,若state=0,则彻底释放锁加锁线程为null)则从等待队列的表头唤醒线程2,重复线程1操作,如果成功则从等待队列中出列加锁线程变为线程2

事务隔离级别

事务的隔离级别

脏读:一个事务读到另外一个事务尚未提交的数据

幻读:以相同的条件,检索以前检索过的数据,发现其他事务插入了新的数据

不可重复读:一个事务在读取后的某个时间再次去读数据发现数据改变

Read uncommit 都没解决

Read commit 只解决了脏读

repeat read 没有解决幻读

serializable 都解决,最高事务级别

CAS的理解及ABA问题

CAS(compare and sort):是乐观锁的实现方式,如果多个线程CAS更新同一个变量,只有一个线程能执行,其他线程都会失败,失败的线程可以再次尝试、有内存地址V,预期原值A,新值B,V==A则V=B否则不会执行

ABA:线程E,F同时拿到了内存地址内的变量A,但是F将变量A改成B,F执行成功E执行失败

解决:在jdk1.5后提供AtomicStampedReference解决ABA

CAS缺点:ABA问题,循环时间长,开销比较大,只能保证一个共享变量的原子操作(锁去解决),浪费cpu资源

慢sql及解决

​ 查询时没有走索引,查询时间过长导致慢sql

​ 解决:对查询性能进行优化

方法加锁和对象加锁区别

  • 方法锁:让成员变量被访问时实现同步

  • 对象锁:让实例对象之间实现同步

  • 类锁:让静态资源实现同步

多用户并发怎么解决?

使用了乐观锁或者悲观锁

  • 乐观锁:CAS自旋锁,是非常经典的乐观锁,并发性能比较好,但是自旋会造成很大的开销,悲观的认为当前环境下并发情况不是很很严重,任务提交时才才判断是否冲突

  • 悲观锁:悲观的认为当前环境下并发情况是很严重的,所有的任务在执行时都要加上锁,保证了安全型,失去了并发性,并发性能差

    乐观锁:无锁,在数据提交时才去判断是否发生冲突,version

    悲观锁:每次访问数据时,都需要加锁

分段锁的理解

优点:不同段的map都能并发执行

缺点:分成很多段时,容易造成内存空间不连续或者碎片化,操作map时竞争同一个锁的概率很小,容易造成更新等操作时间过长,分段锁性能降低

sql语句使用where时尽量不要使用哪些语法?

使用where时不要使用变量或者表达式(!=,>,<,1=1等)以及in和or会导致不走索引

MVCC理解及实现原理

mvcc:多版本并发控制,是一种解决读写冲突的无所并发机制,提高并发性能,让其并发操作数据库时,读操作不阻塞写操作,写操作不阻塞读操作,解决了脏读幻读不可重复的等隔离问题

实现原理:在表中默认创建了三个字段分别代表事务id,索引,和指向日志的指针,用排它锁锁定该行记录,将记录复制到undolog日志里面然后进行事务,若成功则提交,不成功则根据指针找到日志中存储的信息

公平锁与非公平锁?

公平锁:是指多个线程按照申请锁得顺序来获取锁 ReetrantLock(boolean),true为公平锁false为非公平锁

非公平锁:是指多个线程并不是按照申请锁得顺序来获取锁,有可能后申请锁的线程优先级高于先前申请的,在高并发情况下,有可能会造成优先级反转或者饥饿现象 Synchronized

可重入锁与不可重入锁?

可重入锁(递归锁):线程可以进入任何一个他已经拥有的锁所同步着的代码块,ReentrantLock,synchronized作用:防止死锁

不可重入锁:相反

死锁:两个或者以上的线程争夺同一共享资源的僵局,如果没有外力干涉这种局面会一直保持下去直到释放资源

class lock implements Runnable {

    private String lockAA;
    private String lockBB;

    public lock(String lockAA, String lockBB) {
        this.lockAA = lockAA;
        this.lockBB = lockBB;
    }


    @Override
    public void run() {
        synchronized (lockAA) {
            System.out.println(Thread.currentThread().getName()+"持有"+lockAA+"尝试获取"+lockBB);
            synchronized (lockBB){
                System.out.println(Thread.currentThread().getName()+"持有"+lockBB+"尝试获取"+lockAA);
            }
        }
    }
}


public class dieLock {

    public static void main(String[] args) {
        String lockAA = "lockA";
        String lockBB = "lockB";
        new Thread(new lock(lockAA, lockBB), "ThreadAAA").start();
        new Thread(new lock(lockBB, lockAA), "ThreadBBB").start();
    }
}

自旋锁:解决阻塞,线程AA和BB,AA获取锁后线程BB进行CAS发现预期原不是null,则陷入while循环,直到AA线程释放锁

public class SpinLockDemo {
	
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==" + "come in");
        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }

    public void myUnLock() {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "==" + "come back");
    }

    public static void main(String[] args) {
        SpinLockDemo lockDemo = new SpinLockDemo();
        new Thread(() -> {
            lockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lockDemo.myUnLock();
        }, "AA").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {

        }
        new Thread(() -> {
            lockDemo.myLock();
            lockDemo.myUnLock();
        }, "BB").start();
    }
}

独占锁:

该锁只能被一个线程持有,synchronized和reentrantLock都是独占锁

共享锁:该锁可以被锁个线程持有

对于ReentrantReadWriteLock其读锁是共享锁,写锁是独占锁

读锁的共享锁可以保证并发读,是非常高效的

class source {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "==正在写入==" + key);
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "==写入完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }

    }

    public void get(String key) {
        lock.readLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + "==正在读");
            TimeUnit.MILLISECONDS.sleep(300);
            Object value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "==读完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}


public class ReadWriteLockDemo {


    public static void main(String[] args) {
        source s = new source();
        for (int i = 1; i <= 5; i++) {
            final int tmp_int = i;
            new Thread(() -> {
                s.put(tmp_int + "", tmp_int + "");
            }, String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            final int tmp_int = i;
            new Thread(() -> {
                s.get(tmp_int + "");
            }, String.valueOf(i)).start();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值