大白话理解,快速拿下JUC(五)

本文介绍了数据库锁的概念,包括悲观锁和乐观锁的工作原理,以及它们在并发控制中的应用。悲观锁不支持并发,保证数据安全但效率低;乐观锁支持并发,通过版本号避免冲突。接着讨论了表锁和行锁,前者对整个表加锁,后者仅锁定特定行,行锁可能导致死锁。读锁和写锁是读写锁的两种状态,读读共享,读写互斥,写锁独占。文章通过Java的ReentrantReadWriteLock展示了读写锁的使用,并提到了锁降级的概念。最后,文章提及了阻塞队列BlockingQueue在并发编程中的作用,它在队列满或空时会阻塞操作。

1、悲观锁和乐观锁

        悲观锁:是不支持并发的,只能一个操作完后另一个再操作,频繁的上锁解锁,效率低但数据安全

        乐观锁:支持并发,可以多个同时操作,但是引入了版本号概念,如果拿到的版本号和数据库的版本号不一致则操作失败,如A和B线程同时去修改余额,A先提交,那么版本号由v1.0改为v1.1后,B再次提交则匹配不上所以提交失败,下图可分别展示两个锁的区别

 2、表锁和行锁

        表锁:就是将整张表进行上锁,其余线程不能进行操作,其效率较低,但不会存在死锁问题

        行锁:比如表里有四条数据,锁第一条数据叫行锁,行锁效率快但会存在存在互相等待死锁

3、读锁和写锁

        读锁:也叫共享锁,可以多个线程同时访问一个数据,会发生死锁问题,比如A线程修改时候需要等B读完之后,B线程修改时候需要等A读完之后,可能造成互相等待的死锁问题

        写锁:也叫独占锁,同一个数据只能一个线程写操作,也会存在死锁问题,比如A写完第一个条数据后去写第二个数据,B线程写完第二个数据后去写第一个数据,互相都没有释放其锁,造成互相等待死锁问题

        总结:一个资源可以被多个线程访问(读锁共享锁),或者被一个线程进行写的操作(写锁独占锁),但是不能同时存在读写线程,读写互斥,读读共享

4、读写锁案例

        使用ReadWriteLock的实现类ReentrantReadWriteLock,可以实现线程安全的读写操作,原理还是上锁机制,先写入再读取机制       

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class lockDemo01 {
    public static void main(String[] args) {
        mapLock mapLock=new mapLock();
        for (int i = 1; i <=5 ; i++) {
            final int number = i;
            new Thread(()->{
                try {
                    mapLock.put(number+"",number+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <=5 ; i++) {
            final int number = i;
            new Thread(()->{
                try {
                    mapLock.get(number+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
class mapLock{
    private volatile Map<String,Object> map=new HashMap<>();

    ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    public void put(String key,Object obj) throws InterruptedException {
        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName()+" 即将进行写入");
        TimeUnit.MICROSECONDS.sleep(300);
        map.put(key,obj);
        System.out.println(Thread.currentThread().getName()+"写入完成:"+key);
        readWriteLock.writeLock().unlock();
    }
    public Object get(String key) throws InterruptedException {
        readWriteLock.readLock().lock();
        System.out.println(Thread.currentThread().getName()+" 即将进行取出");
        TimeUnit.MICROSECONDS.sleep(300);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"取出完成:"+key);
        readWriteLock.readLock().unlock();
        return o;
    }
}

 5、读写锁优缺点

        优点:读读共享,可以多个资源同时读取

        缺点: a、锁饥饿问题,比如有100个读的操作,一个写的操作,那么可能都忽略了写的操作。b、读时候不能写,只有读完之后才能写,写操作可以读

6、锁降级

        将写入锁降级为读锁,其流程如下图所示,其读锁不可升级为写锁

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class redwirtDemo01 {
    public static void main(String[] args) {
        ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock red = readWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock write = readWriteLock.writeLock();
        //写锁加锁
        write.lock();
        System.out.println("准备开始操作了");
        //获取读锁
        red.lock();
        System.out.println("锁降级成功");       
        //释放写锁 降级处理
        write.unlock();
        //释放读锁
        red.unlock();
    }
}

以上代码充分说明了锁降级过程,写锁可以降级为读锁,但是读锁不能升级为写锁,以下代码实验验证,经一下验证,读锁后获取不到写锁,升级成功打印不出,jvm未能结束

7、阻塞队列BlockingQueue

        阻塞队列,顾名思义,首先它是一个队列, 通过一个共享的队列,可以使得数据
由队列的一端输入,从另外一端输出;
        队列有个特点就是先进先出,就像一个双向的管道,先进去的,从另一端可以先出去,依次出去,栈的特点正好相反,是后进先出,像一个水桶,最后放进水桶的在最上边,可以第一个取到
       
        当队列是空的,从队列中获取元素的操作将会被阻塞
        当队列是满的,从队列中添加元素的操作将会被阻塞

 

IOC即控制反转(Inversion of Control),简单来说,就是把对象的创建和管理的控制权从代码内部转移到外部容器。 举个做菜的例子,传统方式下,一个人要做菜,就需要自己去准备食材、调料等,也就是在代码里自己去创建所依赖的对象。比如一个厨师类 `Chef` 要做红烧肉,就得自己去创建猪肉对象、调料对象等。 ```python class Pork: def __init__(self): print("准备猪肉") class Seasoning: def __init__(self): print("准备调料") class Chef: def __init__(self): self.pork = Pork() self.seasoning = Seasoning() def cook_stewed_pork(self): print("用准备好的猪肉和调料做红烧肉") chef = Chef() chef.cook_stewed_pork() ``` 而使用IOC的方式,就好像有一个专门的后勤部门,会把厨师做菜所需的食材和调料都准备好,厨师只需要专注于做菜就行。在代码里,就是有一个容器来创建和管理对象,然后把对象注入到需要使用的类中。 ```python class Pork: def __init__(self): print("准备猪肉") class Seasoning: def __init__(self): print("准备调料") class Chef: def __init__(self, pork, seasoning): self.pork = pork self.seasoning = seasoning def cook_stewed_pork(self): print("用准备好的猪肉和调料做红烧肉") # 后勤部门(容器) pork = Pork() seasoning = Seasoning() chef = Chef(pork, seasoning) chef.cook_stewed_pork() ``` 在这个例子中,厨师类 `Chef` 不再自己创建猪肉和调料对象,而是由外部提供,这就是控制反转。原本对象的创建和管理是由 `Chef` 类自己控制,现在交给了外部,也就是后勤部门(容器)来控制。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值