javalock(八)ReentrantReadWriteLock

ReentrantReadWriteLock:
同时实现了共享锁和排它锁。内部有一个sync,同时实现了tryAcquire/tryReleases、tryAcquireShared/tryReleasesShared,一共四个函数,然后ReentrantReadWriteLock内部还实现了一个ReadLock和一个WriteLock,ReadLock和WriteLock都实现了lock和unlock函数,然后ReadLock和WriteLock是对同一个Sync对象的封装,不同之处在于ReadLock的lock函数调用的是Sync.tryAcquireShared,unlock调用的是tryReleasesShared,而WriteLock的lock函数调用的是Sync.tryAcquire/Sync.tryReleases。还有ReadLock重载了newCondition,直接抛异常,因为Condition会调用isHeldExclusive来判断当前线程是否拥有排它锁,而ReadLock是共享锁,这矛盾了,所以ReadLock重载了newCondition函数,然后直接抛异常,而WriteLock则可以正常newCondition,因为WriteLock是排它锁,所以可以支持condition,换句话说:只有排它锁才支持condition。

ReentrantReadWriteLock:
锁资源由两部分组成{state,Holder},state是一个int,用来记录已经获取读锁的线程数和已经获取写锁的线程数,state的32位字节分成了两部分,高16位表示已经获取读锁的线程数,低16位表示写者重入的次数,因为写锁是排它锁,也就是说只会有一个线程获取写锁,所以如果state低16位不为0就表示有人获得了写锁,然后这里就直接用低16位来记录写锁重入的个数,如果读者数不为0,则写者数必定为0,如果写者数为0则读者数必定为0,也就是说读写锁互斥。holder则是记录锁的获取信息,因为是reentrant即可冲入锁,也就是说可能出现这种情况:多个线程同时获取了读锁,然后多个线程又多次readLock.lock,所以就需要一个结构体来记录当前线程是否获得了读锁以及当前线程重入的次数,所以holder是一个threadlocal变量,每个线程都有一份,如果当前线程没有获得该锁,则删除该threadLocal,前面说了写锁重入次数直接用state低16位记录,并且用父类的owner来记录谁获得了写锁,所以写锁不用holder,holder只用于读锁。注意ReentrantLock不需要holder的原因是ReentrantLock是排它锁,最多只有一个线程能获得锁,而父类提供的state和owner变量就足够了,所以不需要holder变量。

一个线程已经获取写锁以后,可以继续获取读锁,但是反过来不行,一个线程获取了读锁后,是不允许再次获取写锁的,原因很简单,如果线程先获取了写锁,那么就能保证其他所有线程都不能获取读锁,所以线程可以安全获取读锁,因为只有他一个人能获取读写权限,而反过来如果线程先获取了读锁,那么就可能还有其他线程此时也获取了读锁,这样就不止一个线程获取了读锁,所以此时是不能获取写锁的。


import java.util.concurrent.TimeUnit;
import java.util.Collection;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

//同时实现了读写锁
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;

    //读锁对象
    private final ReentrantReadWriteLock.ReadLock readerLock;
    //写锁对象
    private final ReentrantReadWriteLock.WriteLock writerLock;

    //读锁对象和写锁对象封装了同一个sync对象
    final Sync sync;

  
    public ReentrantReadWriteLock() {
        this(false);
    }

    //默认是非公平锁
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        //readLock和writeLock都是使用的同一个ReentrantReadWriteLock对象
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

  
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 6317671515068378041L;

      

        //读者数偏移,读者数=state>>SHARED_SHIFT
        static final int SHARED_SHIFT   = 16;
        //读者数+1就直接加SHARED_UNIT就行
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        //最大读者数
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        //写者重入次数数掩码,写者数=state&&EXCLUSIVE_MASK
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

      
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
      
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

        //定义的holder类,记录了当前线程id,以及加读锁的次数
        //如果线程获得了读锁,就会拥有一个holdCounter对象
        //如果线程没有获取读锁,就会删除holdCounter对象
        //因为HoldCounter对象被用作ThreadLocal对象
        static final class HoldCounter {
            //记录读锁重入的次数
            int count = 0;
            //线程id
            final long tid = getThreadId(Thread.currentThread());
        }


        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }
        //这里解释一下readHolds/cachedHoldCounter/firstReader/firstReaderHoldCount
        //这些变量都只有一个目的:如果当前线程持有读锁,则返回本线程重入次数
        //readHolds是一个ThreadLocal变量,其他三个都不是ThreadLocal
        //本来要返回本线程重入次数直接返回ThreadLocal内保存的count就行了
        //但是ThreadLocal.get可能比较慢,所以为了优化,
        //就用了两个变量firstReader/cachedHolderCount
        //firstReader记录的是当没有任何获取读锁时,记录第一个获得读锁的线程的thread对象
        //cachedHolderCount记录的是上次获取读锁的线程的id
        //readHolds记录的是本线程的信息,是threadLocal
        //然后如果有人要获取当前线程读锁的重入次数,那么获取逻辑是这样的:
        //先看能不能从firstReader读,如果firstReader就是当前线程
        //那么就直接返回firstReadHoldCount,就不用去读threadLocal了
        //如果firstReader不是当前线程,则尝试去cachedHolderCount读
        //如果上一次获取读锁的线程就是当前线程,那么ok,可以直接从cachedHolderCount读
        //这样就不用去读ThreadLocal了,如果都失败了,那么就只能去读readHolds了
        //而读threadLocal肯定是比较慢的。。。(花里胡哨的,不过文档说有助于提高并发效率。。。)
        private transient ThreadLocalHoldCounter readHolds;
        private transient HoldCounter cachedHoldCounter;
        private transient Thread firstReader = null;
        private transient int firstReaderHoldCount;

        Sync() {
            readHolds = new ThreadLocalHoldCounter();
            setState(getState()); 
        }

      

      
        abstract boolean readerShouldBlock();

      
        abstract boolean writerShouldBlock();

      
        //释放写锁
        protected final boolean tryRelease(int releases) {
            //首先判断当前线程是否拥有锁:直接判断owner是不是当前线程,如果是则拥有
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //然后扣减写锁计数,写锁是低16位,所以可以直接减
            int nextc = getState() - releases;
            //判断写锁重入计数是否为0
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                //如果是则设置owner为null
                setExclusiveOwnerThread(null);
            //然后设置state
            //笔记:在释放写锁前,读者数必定为0
            setState(nextc);
            return free;
        }

        //获取读锁
        protected final boolean tryAcquire(int acquires) {

            //获取当前线程
            Thread current = Thread.currentThread();
            //获取锁资源状态
            int c = getState();
            //计算写锁重入次数个数
            int w = exclusiveCount(c);

            //如果c!=0,表示有人获得了读锁或者有人获得了写锁
            if (c != 0) {
                //c!=0,但是w=0,表明有人获得了读锁
                //c!=0,并且w!=0,表示有人获得了写锁,所以还需要判断是不是自己获得了写锁
                //所以这里就是如果有人获得了读锁或者获取写锁的线程不是自己,那么本次写锁获取失败
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //如果是自己获得了写锁,则判断可重入次数是否超过了(2^16-1)次
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //如果没有,则表示成功获取锁资源,返回true
                //注意:此时是线程已经获得了锁,并且是自己,
                // 也就是说只有本线程可以修改state,所以此时无需用cas操作
                setState(c + acquires);
                return true;
            }
            //如果c==0表示此时没有任何人获得锁
            //因为ReentrantReadWrite支持公平或者非公平锁,所以writeShouldBlock是一个抽象方法
            //公平方式:看aqs的sync list是否有节点等待,如果有则返回true,表示当前线程不能获取锁
            //非公平方式:writeShouldBlock直接返回false,表示立即尝试获取锁
            if (writerShouldBlock() ||
                //如果不需要阻塞,则立即通过cas尝试获取锁
                !compareAndSetState(c, c + acquires))
                //如果cas获取失败就返回false表示本次获取锁失败
                return false;
            //获取成功就设置锁拥有者为自己
            setExclusiveOwnerThread(current);
            return true;
        }

        //释放读锁
        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            //此处到下面for循环前面的都是为了更新当前线程的读锁重入次数

            //如果当前线程就是自己,那么直接更新firstReaderHoldCount
            //就不用去写threadLocal了
            if (firstReader == current) {
                //如果重入次数为1,那么-1之后就表示释放掉了
                //所以直接把firstReader设置为null
                //这样,firstReader是无效的,那么firstReaderHoldCount就肯定失效了
                //所以此处只设置firstReader=null,而没有扣减firstReaderHoldCount
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    //否则读锁重入次数大于1,释放一次后,线程还是拥有读锁
                    //所以firstReader不用变
                    firstReaderHoldCount--;
            } else {
                //如果自己不是firstReader,那么再尝试cachedHoldCounter
                //即尝试自己是不是上一次访问的线程
                HoldCounter rh = cachedHoldCounter;
                //如果缓存的holder为null或者缓存的holder不是当前线程的
                if (rh == null || rh.tid != getThreadId(current))
                    //那么就表示从cachedHoldCounter读取失败
                    //此时就只能去读threadLocal了
                    rh = readHolds.get();
                //此时rh为cachedHoldCounter或者readHolds
                int count = rh.count;
                //如果count<=1,那么释放一次后就为0
                //所以此时就要移除threadLocal
                //免得内存泄漏,比如一个线程获取了读锁,释放后就永不再是用这个rw锁了
                if (count <= 1) {
                    //移除threadLocal
                    readHolds.remove();
                    if (count <= 0)
                        //如果count<=0就表示自己没有持有读锁,却常是释放读锁
                        throw unmatchedUnlockException();
                }
                //当前线程重入次数-1
                --rh.count;
            }
            //更新完读锁重入次数,那么下面就是通过cas更新state也就是更新读锁总数
            for (;;) {
                //获取读锁总次数
                int c = getState();
                //-1
                int nextc = c - SHARED_UNIT;
                //cas设置state
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

        private IllegalMonitorStateException unmatchedUnlockException() {
            return new IllegalMonitorStateException(
                "attempt to unlock read lock, not locked by current thread");
        }

        //尝试获取读锁
        //!!!在持有写锁的状态下允许获取读锁,但是反过来持有读锁时是不允许再次获取写锁
        //也就是说支持锁降级(当然,这里没有降级),但是不支持锁升级
        protected final int tryAcquireShared(int unused) {

            //获取当前线程对象
            Thread current = Thread.currentThread();
            //获取锁资源状态
            int c = getState();
            //判断是否有线程持有写锁,如果有,则判断是不是当前线程
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                //如果不是,则返回-1表剩余读锁资源不足,读锁获取失败
                return -1;
            //走到此处,要么无人获取写锁,要么自己获取了写锁
            //先获取读者计数
            int r = sharedCount(c);
            //判断本次获取读锁是否应该阻塞,分公平和非公平方式
            //非公平方式:如果aqs队列中第一个等待的线程是要获取写锁,那么本次读者应该等待
            //也就是避免写进程长时间获取不到读锁,也就是说如果第一个等待的线程是要获取读锁,
            //那么本线程会立即获取读锁,不管后面的,也就是说对读不公平,但是对写带点公平
            //公平方式:如果aqs队列中有等待的进程,则本次获取read需要阻塞
            // 可能是因为达到总读锁上限了,因为state用于读者锁计数的只有16位
            if (!readerShouldBlock() &&
                //如果没有达到总读者锁重入计数
                r < MAX_COUNT &&
                //那么就尝试获取读锁
                compareAndSetState(c, c + SHARED_UNIT)) {
                //下面就是更新线程对应的读锁重入次数了
                //r==0表示本线程是第一个获得读锁的线程
                if (r == 0) {
                    //那么就设置firstReader为本线程
                    firstReader = current;
                    //并且读锁重入次数为1
                    firstReaderHoldCount = 1;
                //如果当前线程正好是第一个读者
                } else if (firstReader == current) {
                    //那么直接更新firstReaderHoldCount就行
                    firstReaderHoldCount++;
                } else {
                    //反之判断cachedHoldCounter是否为当前线程
                    HoldCounter rh = cachedHoldCounter;
                    //如果cached为null或者cached不是当前线程
                    if (rh == null || rh.tid != getThreadId(current))
                        //则更新cached为当前线程的readHold
                        cachedHoldCounter = rh = readHolds.get();
                    //如果cached是自己,如果count=0表示我们之前已经删除了
                    //那么直接设置readHolds等于我们缓存的,就不用重新创建了
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    //更新本线程读锁重入次数
                    rh.count++;
                }
                //返回获取锁成功
                return 1;
            }
            //如果获取锁失败或者本线程获取读锁需要阻塞,则走重逻辑获取锁
            //重逻辑和上面的逻辑几乎一样
            return fullTryAcquireShared(current);
        }

        //while cas获取读锁
        final int fullTryAcquireShared(Thread current) {
          
            HoldCounter rh = null;
            for (;;) {
                //获取锁状态
                int c = getState();
                //判断是不是有人获取了写锁
                if (exclusiveCount(c) != 0) {
                    //如果有,则再判断是不是自己
                    if (getExclusiveOwnerThread() != current)
                        //如果不是,则返回失败
                        return -1;
                  
                    //如果本次读者需要阻塞:
                    //非公平方式:如果aqs队列中第一个等待的线程是要获取写锁,那么本次读者应该等待
                    //也就是避免写进程长时间获取不到读锁,也就是说如果第一个等待的线程是要获取读锁,
                    //那么本线程会立即获取读锁,不管后面的,也就是说对读不公平,但是对写带点公平
                    //公平方式:如果aqs队列中有等待的进程,则本次获取read需要阻塞
                    // 可能是因为达到总读锁上限了,因为state用于读者锁计数的只有16位
                } else if (readerShouldBlock()) {
                    //这里又是一样的逻辑,用来更新count的,懒得写了
                    if (firstReader == current) {
                      
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //cas操作尝试获取锁
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    //下面又是更新count的逻辑了,和上面一样,略
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; 
                    }
                    //返回1表示读锁资源剩余1
                    //这里是固定返回1,就是说不管获取多少次读锁,剩余读锁资源总是1
                    return 1;
                }
            }
        }

        //和tryAcquire逻辑一模一样,唯一区别就是这里即使线程拥有写锁,也是通过cas更新state
        //略
        final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {
                int w = exclusiveCount(c);
                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;
        }

        //和fullTryAcquireShared逻辑几乎一模一样,略
        final boolean tryReadLock() {
            Thread current = Thread.currentThread();
            for (;;) {
                int c = getState();
                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 {
                        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;
                }
            }
        }

        //判断当前线程是否拥有写锁
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

      
        //写锁支持条件变量,writeLock的newCondition中调用这个函数
        //注意:读锁是不支持的,readLock的newCondition函数则是直接跑UnSupport异常
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        //如果写锁已分配,获取持有写锁的线程对象
        final Thread getOwner() {
            return ((exclusiveCount(getState()) == 0) ?
                    null :
                    getExclusiveOwnerThread());
        }

        //获取读锁总次数
        final int getReadLockCount() {
            return sharedCount(getState());
        }
        //判断是否有现成是否已经获得了写锁
        final boolean isWriteLocked() {
            return exclusiveCount(getState()) != 0;
        }

        //获取写锁重入次数,就是state低15位
        final int getWriteHoldCount() {
            return isHeldExclusively() ? exclusiveCount(getState()) : 0;
        }

        //获取当前线程读锁重入次数
        final int getReadHoldCount() {
            if (getReadLockCount() == 0)
                return 0;

            Thread current = Thread.currentThread();
            //先尝试从firstReader读取,
            if (firstReader == current)
                return firstReaderHoldCount;
            //如果firstReader不是自己,则再尝试从cachedHolderCount读取
            HoldCounter rh = cachedHoldCounter;
            if (rh != null && rh.tid == getThreadId(current))
                return rh.count;

            //如果cachedHolderCount也不是自己,那么最后才去读threadLocal,即读readHolder
            int count = readHolds.get().count;
            if (count == 0) readHolds.remove();
            return count;
        }

      
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            readHolds = new ThreadLocalHoldCounter();
            setState(0); 
        }

        final int getCount() { return getState(); }
    }

    //非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        //本次获取写锁是否该阻塞?答案是:用不阻塞
        final boolean writerShouldBlock() {
            return false; 
        }
        //本次获取读锁是否应该阻塞?
        //答案是如果是如果第一个等待的线程是写锁,则本次获取读锁需要阻塞
        //反之如果是读锁,则不阻塞,对读锁不公平,对写锁稍显公平
        final boolean readerShouldBlock() {
            return  apparentlyFirstQueuedIsExclusives();
        }

    }

    //公平方式
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        //本次获取写锁是否需要阻塞?如果等待队列不为空则需要阻塞
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        //本次获取读锁是否需要阻塞?如果等待队列不为空则需要阻塞
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }


    //下面就是ReadLock和WriteLock了,这两都是对上面的Sync的一个简单封装
    //ReadLock和WriteLock的各种函数都是简单的转调用Sync的相关函数,
    //一眼就能看明白,略


    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();
        }

      
        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();
        }

      
        public String toString() {
            int r = sync.getReadLockCount();
            return super.toString() +
                "[Read locks = " + r + "]";
        }
    }
  
    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);
        }

        //写锁支持条件变量,就是AQS的条件变量
        public Condition newCondition() {
            return sync.newCondition();
        }

      
        public String toString() {
            Thread o = sync.getOwner();
            return super.toString() + ((o == null) ?
                                       "[Unlocked]" :
                                       "[Locked by thread " + o.getName() + "]");
        }

      
        public boolean isHeldByCurrentThread() {
            return sync.isHeldExclusively();
        }

      
        public int getHoldCount() {
            return sync.getWriteHoldCount();
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线程池ForkJoinPool详解

由一道算法题引发的思考 算法题&#xff1a;如何充分利用多核CPU的性能&#xff0c;快速对一个2千万大小的数组进行排序&#xff1f; 这道算法题可以拆解来看&#xff1a; 1&#xff09;首先这是一道排序的算法题&#xff0c;而且是需要使用高效的排序算法对2千万大小的数组…

python08-序列02-字典dict、集合set

一、字典&#xff08;dict&#xff09;&#xff1a;可变数据类型 1-1、字典的特点 字典是可变数据类型&#xff08;list也是&#xff09;&#xff0c;具有增、删、改等一系列的操作&#xff1b;字典中的元素是无序的&#xff08;hash&#xff09;key必须唯一&#xff0c;value…

【Java项目】基于SpringBoot的【旅游管理系统 】

【Java项目】基于SpringBoot的【旅游管理系统 】 技术简介&#xff1a;本系统使用JAVA语言开发&#xff0c;采用B/S架构、Spring Boot框架、MYSQL数据库进行开发设计。 系统简介&#xff1a;&#xff08;1&#xff09;管理员功能&#xff1a;可以管理个人中心、用户管理、景区分…

UE5 跟踪能力的简单小怪

A、思路 1、用素材的骨骼网格体创建小怪BP&#xff0c;绑定新的小怪控制器。 2、控制器的事件开始时&#xff0c;获取玩家状态&#xff0c;指定AI小怪自动向玩家移动。 复杂的AI需要用强大功能如黑板、行为树。 而简单的AI则可以用简单方法实现&#xff0c;杀鸡不用牛刀。视…

渗透测试学习笔记(五)网络

一.IP地址 1. IP地址详解 ip地址是唯一标识&#xff0c;一段网络编码局域网&#xff08;内网&#xff09;&#xff1a;交换机-网线-pcx.x.x.x 32位置2进制&#xff08;0-255&#xff09; IP地址五大类 IP类型IP范围A类0.0.0.0 到 127.255.255.255B类128.0.0.0 到191.255.25…

Windows 下 Anaconda的安装与配置 GPU 版

给之前的电脑安一下深度学习环境 判断是否有NVIDIA GPU Ctrl Shift Esc 打开任务管理器 带此字眼表示有 NVIDIA GPU 安装Anaconda anaconda 打开邮箱会看到下载链接 这里建议修改为其他盘,要不然下载的包和创建的环境都在C盘&#xff0c;占用空间 三个都打钩 取…

flutter --no-color pub get 超时解决方法

新建Flutter项目后&#xff0c;运行报错&#xff0c;需要执行pub get 点击Run ‘flutter pub get’ … … … 卡着&#xff0c;不动了&#xff0c;提示超时 是因为墙的问题 解决方案&#xff1a; 添加以下环境变量 变量名: PUB_HOSTED_URL 变量值: https://pub.flutter-io.cn …

Marin说PCB之POC电路layout设计仿真案例---06

我们书接上回啊&#xff0c;对于上面的出现原因我这个美女同事安娜说会不会你把POC电感下面的相邻两层的CUT_OUT的尺寸再去加大一些会不会变得更好呢&#xff1f;这个难道说是真的有用吗&#xff1f;小编我先自己算一卦看下结果。 本期文章我们就接着验证通过改善我们的单板POC…

Node.js 构建简单应用

在 Node.js 中构建一个简单应用通常包括以下几个步骤&#xff1a; 安装 Node.js设置项目目录初始化项目创建服务器并处理请求和响应 接下来&#xff0c;我们将一步步介绍如何用 Node.js 构建一个简单的 HTTP 应用程序。 1、安装 Node.js 首先确保系统上已安装 Node.js 和 n…

Cesium 无人机航线规划(航点航线)

航线规划实现定制航线&#xff0c;一键巡检功能 小镜头模拟的是此方向的拍照效果&#xff0c;觉得合适可以打个拍照印记 设置里可调控参数 保存后反显的样子&#xff0c;主要是为了区分航线

rfid标签打印开发指导

使用java连接斑马打印机&#xff0c;开发rfid标签打印功能 1.引用斑马打印机的SDKjar包 ZSDK_API.jar 将这个jar文件放到项目的lib目录下&#xff0c;没有就新建一个。 然后点击 File–Project Sreucture–Modules 点击加号 选择对应jar包即可 2.代码开发 1.打印机连接地址…

vue-office:Star 4.2k,款支持多种Office文件预览的Vue组件库,一站式Office文件预览方案,真心不错

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 vue-office 是一个支持多种文件格式&#xff08;docx、excel、pdf、pptx&#xff09;预览的Vue组件库&#xff0c;它不仅支持Vue2和Vue3&#xff0c;还…

Docker介绍、安装、namespace、cgroup、镜像-Dya 01

0. 容器简介 从生活上来说&#xff0c;容器是一种工具&#xff0c;可以装东西的工具&#xff0c;如衣柜、背包、行李箱等等。 从IT技术方面来说&#xff0c;容器是一种全新的虚拟化技术&#xff0c;它提高了硬件资源利用率&#xff0c;结合k8s还可以让企业业务快速横向扩容、业…

Kube-state-metrics 可观测性最佳实践

Kube-state-metrics 介绍 Kube-state-metrics 是 Kubernetes 生态系统中的一个开源项目&#xff0c;主要用来收集和报告集群中各种资源的实时状态信息。 工作原理 Kube-state-metrics 连接到 Kubernetes API 服务器&#xff0c;并公开一个 HTTP 端点&#xff0c;提供集群中各…

Pycharm配置Python开发环境

Pycharm配置Python开发环境 在之前的文章中,安装好了Pyhton和Pycharm。 打开Pycharm,如下图 配置完成之后,如下图所示:

scala中模式匹配的应用

package test34object test6 {case class Person(name:String)case class Student(name:String, className:String)// match case 能根据 类名和属性的信息&#xff0c;匹配到对应的类// 注意&#xff1a;// 1 匹配的时候&#xff0c;case class的属性个数要对上// 2 属性名不需…

PyQt5学习笔记

P95 绝对布局 绝对布局&#xff0c;使用move方法&#xff0c;操作坐标来控件控件的位置。 import sys from PyQt5.QtWidgets import *绝对布局&#xff0c;使用move方法&#xff0c;操作坐标来控件控件的位置。class MyWin(QWidget):def __init__(self):super().__init__()#…

Python3.13安装和配置

Python3.13安装和配置 一、Python的下载 点击下面的下载链接,下载需要的版本。以3.13版本为例。如下图所示: 3.13.0下载地址(windows)3.13.0下载地址(windows) 二、安装 下载完成后,双击安装文件。 <

探索Linux中的Zombie僵死进程

文章目录 探索Linux中的Zombie僵死进程什么是Zombie僵死进程&#xff1f;僵死进程的产生原因如何识别僵死进程&#xff1f;如何清理僵死进程&#xff1f;僵死进程对系统的影响总结 探索Linux中的Zombie僵死进程 在Linux系统中&#xff0c;进程管理是一个非常重要的主题&#x…

win11 C盘出现感叹号解决方法

出现感叹号&#xff0c;原因是对C盘进行了BitLocker驱动器加密操作。如果想去除感叹号&#xff0c;对C盘进行BitLocker解密即可。 步骤如下&#xff1a; 1.点击Windows搜索框 2.搜索框内输入 系统 3.按下回车&#xff0c;进入系统界面 4.点击隐私和安全性 点击BitLocker驱…