ReentrantReadWriteLock

news2024/11/22 13:41:59

关于读写锁状态的存取

// ReentrantReadWriteLock.Sync
static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }// 高16位存储读锁
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }// 低16位存储写锁
// 设置读锁的状态
compareAndSetState(c, c + SHARED_UNIT)
// 设置写锁的状态
compareAndSetState(c, c + 1)

使用state(int类型,32位)的高16位存储读锁状态,低16位存储写锁状态

读写锁的结构

ReentrantReadWriteLock本身不是一把锁,但是它管理着两把锁,读锁和写锁。写锁是独占锁,读锁是共享锁。

这里ReadWriteLock只是一个接口,并不是一把锁。

private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;

公平锁和非公平锁

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }
    final boolean readerShouldBlock() {// 返回false会尝试去获取锁
        return apparentlyFirstQueuedIsExclusive();
    }
}
// 这个方法指用在获取读锁上,即获取共享锁上
// 即下一个要被唤醒的节点是不是独占节点,即这个节点是不是在申请写锁
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    // 下一个要唤醒的节点不是共享节点,就返回true
    // 即如果下一个要唤醒的是申请写锁的线程,就阻塞读锁的获取
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    // h!=t证明起码有一个线程在等待 s.thread!= currentThread 因为读,写锁都是可重入的,所以,如果等待的这个线程正好是当前线程
    // 可以直接放行让它获取锁
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

在ReentrantLock中,公平锁与非公平锁的区别是,非公平锁不管同步队列中有没有线程在排队,都会先去获取锁。而公平锁,就是直接先去排队。

那么同样的定义,在ReentrantReadWriteLock中也体现得很明显。

FairSync和NonFairSync都重写了writerShouldBlock,readerShouldBlock方法。

writerShouldBlock用在获取独占锁,也就是写锁的方法上。

// Sync  acquire方法会调用tryAcquire方法获取锁,acquire方法会被WriteLock.lock方法调用,即获取写锁的时候调用
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())// c != 0,w == 0,证明有线程获取了读锁
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    // 如果是公平锁,writerShouldBlock将会看前面有没有线程在排队,有排队的,writerShouldBlock才返回true,即这里直接返回false(获取锁失败)
    // 如果是非公平锁,这里返回false,会调用CAS设置state,设置失败才返回false
    // 所以,就一个writerShouldBlock方法就可以实现了公平锁和非公平锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

获取写锁流程图如下:

readerShouldBlock方法被tryAcquireShared和fullyTryAcquireShared调用

// Sync tryAcquireShared会被acquireShared调用,acquireShared被ReadLock.lock调用,即获取读锁的时候调用
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

 读锁和写锁

public static class ReadLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;

    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquireShared(1);
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.tryReadLock();// 这是唯一一个没有用AQS方法的方法
    }
    
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.releaseShared(1);
    }

    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
}
// ReentrantReadWriteLock.Sync
final boolean tryReadLock() {
    Thread current = Thread.currentThread();
    for (;;) {
        int c = getState();
        // exclusiveCount指的是获取写锁的数目
        // 如果有别的线程已经获取了写锁,则失败(如果是同一个线程获取了写锁,则无所谓,因为同一个线程的读写肯定不会冲突)
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return false;
        int r = sharedCount(c);// 读锁的数目
        if (r == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {// 获取读锁成功
            if (r == 0) {// 如果原来没有线程获取到读锁
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                // 一个同步器有一个cachedHoldCounter,一个Lock有一个同步器
                // cachedHoldCounter是上一个获取到读锁的线程的获取锁的数目
                // 如果当前线程获取了读锁,更新占有锁的数目
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;// 同一个线程
            }
            return true;
        }
    }
}

可以看到ReadLock中,除了tryLock方法,其他调的都是AQS提供的方法。其中lock方法也在公平锁和非公平锁中讲过了。

tryLock的流程也跟lock的流程基本一致,除了lock中加了readerShouldBlock方法的判断。这里就不详解了。

public static class WriteLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;

    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquire(1);
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock( ) {
        return sync.tryWriteLock();// 也是只有这个方法是自定义的
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.release(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
    
    public int getHoldCount() {
        return sync.getWriteHoldCount();
    }
}
// ReentrantReadWriteLock.Sync
final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c != 0) {// 可能有写锁或读锁被获取了
        int w = exclusiveCount(c);
        // w == 0表示,如果有线程获取了读锁,就直接失败吗(为什么)不是同一个线程可以直接获取读锁和写锁吗?如果它之前已经获取了读锁,现在再来获取写锁会直接失败吗?
        // 或者有线程获取了写锁,但是只要不是当前线程,就直接失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

写锁与读锁结构也基本一致,自己实现了tryLock方法,流程与tryAcquire基本一致,这里不细讲。

读写锁的同步队列

读写锁虽然持有了两把锁,但是只有一个Sync,即只有一个同步队列。

那只有一个同步队列,是怎么管理独占节点和共享节点的阻塞和唤醒的呢?

获取锁在上面都讲了,现在讲讲读写锁是怎么释放的。

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

这是写锁的释放,总体来说比较简单。

看看当前线程还持有多少个锁,如果释放完以后,还持有0个锁,就将当前独占的线程设为空。

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}

实例

构建几个场景:

场景一:

读+读+写(这里读、写分别代表申请读写的线程,按+的先后顺序,表示线程申请读写锁的时间顺序,比如在这里就代表,线程1申请了读锁,之后,线程2申请读锁,线程3申请写锁),这时候同步队列应该是什么样的呢?

public class LockExample {
    public void testReadWriteLock() {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        CountDownLatch countDownLatch = new CountDownLatch(3);
        t1ReadLock(readWriteLock,countDownLatch,0,3);
        t2WriteLock(readWriteLock,countDownLatch,2,3);
        t3ReadLock(readWriteLock,countDownLatch,1,3);
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {

        }
    }
}

以下为线程对应的工作,在所有场景中会复用。

 private void t1ReadLock(ReentrantReadWriteLock readWriteLock,CountDownLatch countDownLatch,int startDelay, int duration) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(startDelay);
                } catch (InterruptedException e) {

                }
                System.out.println("thread1 begin fetch readLock");
                readWriteLock.readLock().lock();
                System.out.println("thread1 has fetch readLock");
                try {
                    System.out.println("======= thread1 do things ==========");
                    TimeUnit.SECONDS.sleep(duration);// 休眠过程中不会放开锁
                } catch (InterruptedException e) {

                } finally {
                    System.out.println("now readWriteLock queue:"+readWriteLock.getQueueLength());
                    System.out.println("======= thread1 do things end ,unlock readLock ==========");
                    readWriteLock.readLock().unlock();
                    countDownLatch.countDown();
                }

            }
        });
        thread1.start();


    }

    private void t2WriteLock(ReentrantReadWriteLock readWriteLock,CountDownLatch countDownLatch,int startDelay,int duration) {
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(startDelay);
                } catch (InterruptedException e) {

                }
                System.out.println("thread2 begin fetch writeLock");
                readWriteLock.writeLock().lock();
                try {
                    System.out.println("======= thread2 do things ==========");
                    TimeUnit.SECONDS.sleep(duration);
                } catch (InterruptedException e) {

                } finally {
                    System.out.println("now readWriteLock queue:"+readWriteLock.getQueueLength());
                    System.out.println("======= thread2 do things end,unlock writeLock ==========");
                    readWriteLock.writeLock().unlock();
                    countDownLatch.countDown();
                }
            }
        });
        thread2.start();
    }

    private void t3ReadLock(ReentrantReadWriteLock readWriteLock,CountDownLatch countDownLatch,int startDelay, int duration) {
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(startDelay);
                } catch (InterruptedException e) {

                }
                System.out.println("thread3 begin fetch readLock");
                readWriteLock.readLock().lock();
                System.out.println("thread3 has fetch readLock");
                try {
                    System.out.println("======= thread3 do things ==========");
                    TimeUnit.SECONDS.sleep(duration);
                } catch (InterruptedException e) {

                } finally {
                    System.out.println("now readWriteLock queue:"+readWriteLock.getQueueLength());
                    System.out.println("======= thread3 do things end unLock ReadLock ==========");
                    readWriteLock.readLock().unlock();
                    countDownLatch.countDown();
                }
            }
        });
        thread3.start();
    }

thread1 begin fetch readLock

thread1 has fetch readLock

======= thread1 do things ==========

thread3 begin fetch readLock

thread3 has fetch readLock

======= thread3 do things ==========

thread2 begin fetch writeLock

now readWriteLock queue:1

======= thread1 do things end ,unlock readLock ==========

now readWriteLock queue:1

======= thread3 do things end unLock ReadLock ==========

======= thread2 do things ==========

now readWriteLock queue:0

======= thread2 do things end,unlock writeLock ==========

从结果可以看出,读+读+写的时候,两个读锁可以并行执行,写锁需要等两个读锁执行完后才能获取锁。看同步队列中,线程数目为1也可以证实这一点。

从代码逻辑来看:

获取写锁的前提是,当前没有任何线程持有读锁,且写锁的获取者为当前线程,或没有线程获取到写锁。

获取读锁的前提是,除了当前线程,其他线程都没有获取到写锁。

所以读+读+写,最终的结论是(读,读+写)

场景二:

读+写+读

public void testReadWriteLock() {
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    CountDownLatch countDownLatch = new CountDownLatch(3);
    t1ReadLock(readWriteLock,countDownLatch,0,3);
    t2WriteLock(readWriteLock,countDownLatch,1,3);// 只调整了写锁和读锁的开始时间
    t3ReadLock(readWriteLock,countDownLatch,2,3);
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {

    }
}

thread1 begin fetch readLock

thread1 has fetch readLock

======= thread1 do things ==========

thread2 begin fetch writeLock

thread3 begin fetch readLock

now readWriteLock queue:2

======= thread1 do things end ,unlock readLock ==========

======= thread2 do things ==========

now readWriteLock queue:1

======= thread2 do things end,unlock writeLock ==========

thread3 has fetch readLock

======= thread3 do things ==========

now readWriteLock queue:0

======= thread3 do things end unLock ReadLock ==========

 

神奇了!如果按场景一的理解,这时候的结果就应该跟场景一的一致。但现在线程3却跟线程2一起阻塞了,这是为什么呢?

原因是,读写锁默认是非公平锁。

非公平锁中重写了readerShouldBlock方法。其中readerShouldBlock方法中

判断,如果下一个要唤醒的节点为写锁,则阻塞当前读锁的获取(至于为什么这么做,可能是因为担心饥饿问题把,毕竟读锁是共享的,如果一直都让读锁共享,很容易导致写锁一直没有机会获取)。

所以,对于读+写+读,最终的顺序就是(读+写+读)

 

附+测试:

对于读+写+读+读,后面两个读线程会并发执行吗?

public void testReadWriteLock() {
    final int DURATION_TIME = 5;
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    CountDownLatch countDownLatch = new CountDownLatch(4);
    t1ReadLock(readWriteLock,countDownLatch,0,DURATION_TIME);
    t2WriteLock(readWriteLock,countDownLatch,1,DURATION_TIME);
    t3ReadLock(readWriteLock,countDownLatch,2,DURATION_TIME);
    t4ReadLock(readWriteLock,countDownLatch,3,DURATION_TIME);
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {

    }
}

thread1 begin fetch readLock

thread1 has fetch readLock

======= thread1 do things ==========

thread2 begin fetch writeLock

thread3 begin fetch readLock

thread4 begin fetch readLock

now readWriteLock queue:3

======= thread1 do things end ,unlock readLock ==========

======= thread2 do things ==========

now readWriteLock queue:2

======= thread2 do things end,unlock writeLock ==========

thread3 has fetch readLock

======= thread3 do things ==========

thread4 has fetch readLock

======= thread4 do things ==========

now readWriteLock queue:0

now readWriteLock queue:0

======= thread3 do things end unLock ReadLock ==========

======= thread4 do things end unLock ReadLock ==========

 

这里后面thread3和thread4是并行执行完的。因为线程2释放的时候,会去释放线程3。线程3在执行doAcquireShared方法的时候,会释放后继节点,即线程4。

场景三:

写+写+读

这其实很简单,写线程是独占锁,所以最终顺序就是写+写+读

public void testReadWriteLock() {
    final int DURATION_TIME = 4;
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    CountDownLatch countDownLatch = new CountDownLatch(3);
    t1ReadLock(readWriteLock,countDownLatch,2,DURATION_TIME);
    t2WriteLock(readWriteLock,countDownLatch,0,DURATION_TIME);
    t5WriteLock(readWriteLock,countDownLatch,1,DURATION_TIME);
    
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {

    }
}

thread2 begin fetch writeLock

======= thread2 do things ==========

thread5 begin fetch writeLock

thread1 begin fetch readLock

now readWriteLock queue:2

======= thread2 do things end,unlock writeLock ==========

======= thread5 do things ==========

now readWriteLock queue:1

======= thread5 do things end,unlock writeLock ==========

thread1 has fetch readLock

======= thread1 do things ==========

now readWriteLock queue:0

======= thread1 do things end ,unlock readLock ==========

 

场景四:

同一个线程,先获取写锁再获取读锁

private void writeToReadLock(ReentrantReadWriteLock readWriteLock) {
    Thread thread = new Thread(()->{
        readWriteLock.writeLock().lock();
        System.out.println("do write things");
        readWriteLock.readLock().lock();
        try {

        } finally {
            System.out.println("unlock writeLock");
            readWriteLock.writeLock().unlock();
            System.out.println("unlock readLock");
            readWriteLock.readLock().unlock();
        }
    });
    thread.start();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/879247.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小白到运维工程师自学之路 第七十三集 (kubernetes应用部署)

一、安装部署 1、以Deployment YAML方式创建Nginx服务 这个yaml文件在网上可以下载 cat nginx-deployment.yaml apiVersion: apps/v1 #apiVersion是当前配置格式的版本 kind: Deployment #kind是要创建的资源类型&#xff0c;这里是Deploymnet metadata: #metadata是该资源…

快手商品详情数据API 抓取快手商品价格、销量、库存、sku信息

快手商品详情数据API是用来获取快手商品详情页数据的接口&#xff0c;请求参数为商品ID&#xff0c;这是每个商品唯一性的标识。返回参数有商品标题、商品标题、商品简介、价格、掌柜昵称、库存、宝贝链接、宝贝图片、商品SKU等。 接口名称&#xff1a;item_get 公共参数 名…

JS实现树形结构、一维数组以及map之间的转换

const treeData[ {id:1, name:中国, children:[ {id:11,name:河南省,children:[{id:111,name:南阳市,children:[{id:1111,name:淅川县,children:null}]},{id:112,name:郑州市,children:[{id:1121,name:中牟县,children:null}]}] }, {id:22,name:广东省,children:[{id:221,name:…

【【verilog典型电路设计之流水线结构】】

verilog典型电路设计之流水线结构 下图是一个4位的乘法器结构&#xff0c;用verilog HDL 设计一个两级流水线加法器树4位乘法器 对于流水线结构 其实需要做的是在每级之间增加一个暂存的数据用来存储 我们得到的东西 我们一般来说会通过在每一级之间插入D触发器来保证数据的联…

创建Azure资源锁

锁的介绍 在Azure中&#xff0c;资源锁是一种用于保护订阅、资源组或者单个资源的机制。它可以防止对受锁定的资源进行删除或修改操作&#xff0c;帮助确保资源的连续可用性和安全性。 Azure中的资源锁可以分为两种类型&#xff1a; 删除锁&#xff08;CanNotDelete&#xf…

实现Excel数据复制分录信息粘贴到金蝶单据体中

>>>适合KIS云专业版V16.0|KIS云旗舰版V7.0|K/3 WISE 14.0等版本<<< 实现Excel数据复制分录信息粘贴到金蝶单据体分录中,在采购订单|采购入库单|销售订单|销售出库单等类型单据中,以少量的必要字段在excel表格中按模板填列好,很方便快捷地复制到金蝶单据表体…

三分钟完美解决你的C盘内存过大爆红

一、清理回收站 二、清理桌面 建议一 不要在桌面放太多图标或者文件会占用过多的内存,可以放到其他盘建议二、 将位置移动到别的盘 三、手动删除下载文件与缓存文件 日常使用中会通过Windows下载各种文件资料到电脑中&#xff0c;它默认也是直接下载在C盘中的。如果我们在以…

ssm+JSP的乡镇自来水收费系统源码和论文PPT

ssmJSP的乡镇自来水收费系统源码和论文PPT014 开发工具&#xff1a;idea 数据库mysql5.7(mysql5.7最佳) 数据库链接工具&#xff1a;navcat,小海豚等 开发技术&#xff1a;java ssm tomcat8.5 一、课题背景与意义 随着我国经济建设迅速发展&#xff0c;乡镇规模日益扩大&am…

RK3568 HDMI接口

一.简介 HDMI接口&#xff0c;中文全称为高清多媒体接口。是一种全数字化视频以及声音发送接口&#xff0c;能够发送没有压缩的音频以及视频信号。HDMI接口可以使用在机顶盒、DVD播放机等设备商。除此之外&#xff0c;HDMI接口还可以同时发送音频以及视频信号&#xff0c;简化…

22款美规奔驰GLS450更换AMG GLS63原厂刹车卡钳系统,刹车效果强悍无比

AMG出品的大六活塞卡钳及大直径开孔刹车碟&#xff0c;所组成的制动套件。这套AMG出品的卡钳为制动生产名厂BREMBO为其代工&#xff0c;刹车碟是高锻钢锻造&#xff0c;耐高温耐用性能较高且打孔设计。

什么是事务,并发带来的事务问题以及事务隔离级别(图文详解)

一、什么是事务&#xff1f; 简单说就是逻辑上的一组操作&#xff0c;要么都执行&#xff0c;要么都不执行。 举个例子&#xff0c;假如小明要给小红转账100元&#xff0c;这个转账会涉及到两个关键操作&#xff1a;①将小明的余额减少100元。 ②将小红的余额增加100元 。但…

【左神算法刷题班】第18节:汉诺塔问题、岛屿问题、最大路径和问题

第18节 题目1&#xff1a;汉诺塔问题&#xff08;变体&#xff09; 体系学习班18节有讲暴力递归的汉诺塔原题。 给定一个数组arr&#xff0c;长度为N&#xff0c;arr中的值只有1&#xff0c;2&#xff0c;3三种 arr[i] 1&#xff0c;代表汉诺塔问题中&#xff0c;从上往下第…

Azure添加网络接口

添加网络接口的意义 在 Azure 上&#xff0c;为虚拟机添加网络接口的意义包括以下几个方面&#xff1a; 扩展网络带宽&#xff1a;通过添加多个网络接口&#xff0c;可以增加虚拟机的网络带宽&#xff0c;提高网络传输速度和数据吞吐量。实现网络隔离&#xff1a;每个网络接口…

网络安全体系架构介绍

网络安全体系是一项复杂的系统工程&#xff0c;需要把安全组织体系、安全技术体系和安全管理体系等手段进行有机融合&#xff0c;构建一体化的整体安全屏障。针对网络安全防护&#xff0c;美国曾提出多个网络安全体系模型和架构&#xff0c;其中比较经典的包括PDRR模型、P2DR模…

2021年06月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;数字放大 给定一个整数序列以及放大倍数x&#xff0c;将序列中每个整数放大x倍后输出。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 输入 包含三行&#xff1a; 第一行为N&#xff0c;表示整数序列的长度(N ≤ 100); 第二行为N个整数(不超过整型范围…

一个脚本 专治杂乱

背景 之前不是自己手动搞了一个COS嘛&#xff0c;直接复制粘贴图片&#xff0c;上传到后端的服务器&#xff0c;返回一个可访问的地址。我在哔哩哔哩上也分享过这样的一期视频。 今天偶尔上服务器一看&#xff0c;我靠&#xff0c;我的文件真的乱&#xff01; 这还得了了&…

【C++精华铺】7.C++内存管理

目录 1. C语言动态内存管理 2. C内存管理方式 2.1 new/delete和new T[]/delete[] 2.1.1 操作内置类型 2.1.2 操作自定义类型 2.2 new/delete和new T[]/delete[]的原理 2.2.1 原理 2.2.2 operator new和operator delete 2.2.3 new T[]的特殊处理&#xff08;可以…

Altium DXP原理图转换成Orcad Capture

买了个开发板&#xff0c;原图是Altium DXP的&#xff0c;但是个人熟悉的Orcad&#xff0c;PCB无所谓了&#xff0c;反正都要重画&#xff0c;但是原理图是件大工程&#xff0c;重画还可能出问题&#xff0c;所以想着把DXP转成Capture格式&#xff0c;查阅了相关文档&#xff0…

【Linux命令详解 | ps命令】 ps命令用于显示当前系统中运行的进程列表,帮助监控系统状态。

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 基本用法2. 显示所有进程3. 显示进程详细信息4. 根据CPU使用率排序5. 查找特定进程6. 显示特定用户的进程7. 显示进程内存占用8. 查看进程树9. 实时监控进程10. 查看特定进程的详细信息11. 查看特定用户的进程统计…

RTT(RT-Thread)串口设备(RTT保姆级教程)

目录 UART串口设备 串口概述 访问串口设备接口 数据发送方法 数据接收方法 串口设备使用流程 串口中断接受实例 串口配置及串口发送 串口中断接收 DMA接收 UART串口设备 串口概述 本章主要介绍串口设备在RT-Thread操作系统中应用层如何使用。关于串口设备的使用&am…