Lock锁和ReentrantLock锁

2023-05-16


前言

JDK 1.5中提供的锁的接口java.util.concurrent.locks.Lock,其提供了一些ReentrantLock, ReentrantReadWriteLock实现类。

参考JDK文档:Java Platform SE 6


目录

前言

Lock接口

ReentrantLock

公平性和非公平性

公平锁与非公平锁的使用示例 

AQS

AQS核心字段

AQS同步器原理

ReentrantLock实现

公平性锁和非公平性锁父类:Sync

公平性锁实现:FairSync

非公平性锁实现:NonfairSync

重入锁实现:

condition

Condition与Object中的wati,notify,notifyAll区别:


Lock接口

Lock实现提供了比使用Synchronized方法和语句更广泛的搜定操作,此操作允许更灵活的结构,可以具有很大的属性,可以支持多个相关的Condition对象

Lock接口的提供的方法有:

图片.png

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
        //todo
        //加锁  未抢到锁的阻塞下来,直到抢到锁才会执行后续的逻辑
        reentrantLock.lock();
        try {
            //可中断的加锁
            reentrantLock.lockInterruptibly();
            //尝试性加锁 如果加锁成功 返回true  加锁失败会返回false  立即返回
            reentrantLock.tryLock(1000,TimeUnit.MILLISECONDS);
            reentrantLock.tryLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //todo
        
        //释放锁
        reentrantLock.unlock();

         Lock对比Synchronized加锁操作

  1. Lock实现类的锁必须是显性的调用加锁、释放锁,且必须成对出现
  2. 加锁操作可以滴方法内部的核心代码片段,加锁粒度更细,意味着并发性更高
  3. 可以通过lockInterruptibly方式添加可中断锁
  4.  可以尝试性加锁,未抢到锁可以立即返回 

ReentrantLock

公平性和非公平性

如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为“饥饿”。而该线程被“饥饿致死”正是因为它得不到CPU运行的机会。解决饥饿的方案被称之为“公平性”,即所有线程均能公平地获得运行机会。通俗讲,如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,反之,是不公平的,也就是说等待时间最长的线程最有机会获取锁,也可以说锁的获取是有序的。

线程饥饿的原因:

  1. 高优先级的线程会比低优先级的线程优先执行
  2. 线程被阻塞在一个等待进入同步块的状态

公平性锁:

在ReentrantLock中通过FairSync实现公平性锁,其实现是基于一个队列来实现的。

非公平锁:

NonfairSync:在锁中保持一个获取锁的线程信息,当释放锁之后再次抢锁时,通过比较正在抢锁的线程和队列头的线程,如果是上一次获取锁的线程,那么该线程具有优先执行权。

ReentrantLock实现了公平锁和非公平锁。

public ReentrantLock(boolean fair) //参数是Boolean  true:公平性锁  false:非公平性锁
public ReentrantLock ()  //默认值是false 

公平锁与非公平锁的使用示例 

在while循环中进行显性加锁lock,通过打印看是哪个线程抢到了cpu的执行机会,然后再释放锁。

public class NonFairAndFairDemo implements Runnable {
    private static Integer num = 0;
    private ReentrantLock rtl;

    public NonFairAndFairDemo(ReentrantLock rtl) {
        this.rtl = rtl;
    }

    @Override
    public void run() {
        while (true) {
            //显性加锁
            rtl.lock();
            num++;
            System.out.println(Thread.currentThread().getName()+":"+num);
            rtl.unlock();
        }
    }

    public static void main(String[] args) {
        //非公平性锁的实现
        ReentrantLock nonFairLock = new ReentrantLock(true);
        new Thread(new NonFairAndFairDemo(nonFairLock),"A").start();
        new Thread(new NonFairAndFairDemo(nonFairLock),"B").start();
    }
}

非公平性锁运行,传入false就是非公平锁,true就是公平锁:

ReentrantLock nonFairLock = new ReentrantLock(false);

由运行结果可以看出两个线程抢到资源后会一直抢占,经过一段时间后才被另一个线程抢到,是不公平的。 

公平性锁运行结果:

ReentrantLock nonFairLock = new ReentrantLock(true);

公平性锁机制保证了先来的锁优先级高,抢到运行机会的概率大,它的实现机制是基于队列的,因此如果A线程先到,那么等释放锁了之后A线程就会先运行,反之B先运行,这也就是说他们之间的运行时有序的,交替执行的。

AQS

实现提供了一个双向队列,将同步失败线程加入到双向队列,(Node节点包含线程信息,状态waitState),AQS中包含了state.  AQS类与子类层级关系如下:

图片.png

AbstractQueuedSynchronizer是CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基础,因此AbstractQueuedSynchronizer是Lock/Executor实现的前提。公平锁、不公平锁、Condition、CountDownLatch、Semaphore等实现的基础。

AQS核心字段

AQS里面有三个核心字段:

private volatile int state;

private transient volatile Node head;

private transient volatile Node tail;

其中state描述的有多少个线程取得了锁,对于互斥锁来说state<=1。

AQS中的state:

state=0 表示锁是空闲状态

state>0 表示锁被占用

state<0 表示溢出

head/tail加上CAS操作就构成了一个CHL的FIFO队列,下面是Node节点的属性:

 static final class Node {
    /** 标记表示节点正在共享模式中等待 */
    static final Node SHARED = new Node();
    /** 标记表示节点正在独占模式下等待 */
    static final Node EXCLUSIVE = null;

    /** 
    * 表示线程已经被取消 
    * 同步队列中的线程因为超时或中断,需要从同步队列中取消。被取消的节点将不会有任何改变
    */
    static final int CANCELLED = 1;


    /** 
    * 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后
    * 继节点,使后继节点的线程得以运行 
    */
    static final int SIGNAL = -1;


    /** 
      * 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()方法 
     *  后,该节点将会中等待队列中转移到同步队列中,加入到对同步状态的获取 
     */
    static final int CONDITION = -2;


    /**
     * 下一次共享模式同步状态获取将会无条件的被传播下去
     */
    static final int PROPAGATE = -3;



    /**
     *   等待状态,仅接受如下状态中的一个值:
     *   SIGNAL:  -1
     *   CANCELLED:   1
     *   CONDITION:   -2
     *   PROPAGATE:   -3
     *   0:  初始化的值
     *
     * 对于正常的同步节点,它的初始化值为0,对于条件节点它的初始化的值是CONDITION。它使用
     * CAS进行修改。
     */
    volatile int waitStatus;

    /**
     *  前驱节点
     */
    volatile Node prev;

    /**
     * 后继节点
     */
    volatile Node next;

    /**
     * 获取同步状态的线程
     */
    volatile Thread thread;

    /**
     * 等待队列中的后继节点。如果当前节点是共享的,那么这个字段是一个SHARED常量,也就是说
     * 节点类型(独占和共享)和等待队列中的后继节点公用同一个字段
     */
    Node nextWaiter;

    /**
     * 如果节点在共享模式下等待则返回true
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 获取前驱节点
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {
    }

    Node(Thread thread, Node mode) { 
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { 
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

volatile int waitStatus:节点的等待状态,一个节点可能位于以下几种状态:

CANCELLED = 1

当前的线程被取消,节点操作因为超时或者对应的线程被interrupt。节点不应该留在此状态,一旦达到此状态将从CHL队列中踢出。

SIGNAL = -1

表示当前节点的后继节点包含的线程需要运行,也就是unpark.节点的继任节点是(或者将要成为)BLOCKED状态(例如通过LockSupport.park()操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpack())它的继任节点。

CONDITION = -2

当前节点在等待condition,也就是在condition队列中.表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。PROPAGATE=-3当前场景下后续的acquireShared能够得以执行

当前节点在sync队列中,等待着获取锁

正常状态,新生的非CONDITION节点都是此状态。

非负值标识节点不需要被通知(唤醒)。

volatile Node prev;此节点的前一个节点。节点的waitStatus依赖于前一个节点的状态。

volatile Node next;此节点的后一个节点。后一个节点是否被唤醒(uppark())依赖于当前节点是否被释放。

volatile Thread thread;节点绑定的线程。

Node nextWaiter;下一个等待条件(Condition)的节点,由于Condition是独占模式,因此这里有一个简单的队列来描述Condition上的线程节点。

节点(Node)是构成CHL的基础,同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程会构建成一个节点并加入到同步器的尾部。CHL的基本结构如下:

image.png

AQS同步器原理

基本的思想是表现为一个同步器,支持下面两个操作:

获取锁:首先判断当前状态是否允许获取锁,如果是就获取锁,否则就阻塞操作或者获取失败,也就是说如果是独占锁就可能阻塞,如果是共享锁就可能失败。另外如果是阻塞线程,那么线程就需要进入阻塞队列。当状态位允许获取锁时就修改状态,并且如果进了队列就从队列中移除。

while(synchronization state does not allow acquire){
   enqueue current thread if not already queued;
   possibly block current thread;
}
dequeue current thread if it was queued;

释放锁:这个过程就是修改状态位,如果有线程因为状态位阻塞的话就唤醒队列中的一个或者更多线程。

update synchronization state;
if(state may permit a blocked thread to acquire)
   unlock one or more queued threads;

要支持上面两个操作就必须有下面的条件:

  • 原子性操作同步器的状态位
  • 阻塞和唤醒线程
  • 一个有序的队列

目标明确,要解决的问题也清晰了,那么剩下的就是解决上面三个问题。

  • 状态位的原子操作

这里使用一个32位的整数来描述状态位,使用CAS操作来修改状态。事实上这里还有一个64位版本的同步器(AbstractQueuedLongSynchronizer),这里暂且不谈。

  • 阻塞和唤醒线程

标准的JAVA API里面是无法挂起(阻塞)一个线程,然后在将来某个时刻再唤醒它的。JDK 1.0的API里面有Thread.suspend和Thread.resume,并且一直延续了下来。但是这些都是过时的API,而且也是不推荐的做法。

在JDK 1.5以后利用JNI在LockSupport类中实现了此特性。

LockSupport.park()

LockSupport.park(Object)

LockSupport.parkNanos(Object, long)

LockSupport.parkNanos(long)

LockSupport.parkUntil(Object, long)

LockSupport.parkUntil(long)

LockSupport.unpark(Thread)

上面的API中park()是在当前线程中调用,导致线程阻塞,带参数的Object是挂起的对象,这样监视的时候就能够知道此线程是因为什么资源而阻塞的。由于park()立即返回,所以通常情况下需要在循环中去检测竞争资源来决定是否进行下一次阻塞。park()返回的原因有三:

  • 其他某个线程调用将当前线程作为目标调用 unpark;
  • 其他某个线程中断当前线程;
  • 该调用不合逻辑地(即毫无理由地)返回。

其实第三条就决定了需要循环检测了,类似于通常写的while(checkCondition()){Thread.sleep(time);}类似的功能。

  • 有序队列

在AQS中采用CHL列表来解决有序的队列的问题。AQS采用的CHL模型采用下面的算法完成FIFO的入队列和出队列过程。对于入队列(enqueue):从数据结构上出发,入列是比较简单的,无非就是当前队列中的尾节点指向新节点,新节点的prev指向队列中的尾节点,然后将同步器的tail节点指向新节点。在AQS中入列的源码如下:

 /**
 * 为当前线程和给定的模式创建节点并计入到同步队列中
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    // 创建一个节点
    Node node = new Node(Thread.currentThread(), mode);
    // 快速尝试添加尾节点,如果失败则调用enq(Node node)方法设置尾节点
    Node pred = tail;
    // 判断tail节点是否为空,不为空则添加节点到队列中
    if (pred != null) {
        node.prev = pred;
        // CAS设置尾节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

/**
 * 插入节点到队列中
 * @param node the node to insert
 * @return node's predecessor
 */
private Node enq(final Node node) {
    // 死循环 知道将节点插入到队列中为止
    for (;;) {
        Node t = tail;
        // 如果队列为空,则首先添加一个空节点到队列中
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // tail 不为空,则CAS设置尾节点
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

从上面源码中我们可以看到,在将节点添加到CHL尾部的时候,使用了一个CAS方法(compareAndSetTail(pred, node)),这里使用CAS的原因是防止在并发添加尾节点的时候出现线程不安全的问题(即有可能出现遗漏节点的情况)

ReentrantLock实现

公平性锁和非公平性锁父类:Sync

static abstract class Sync extends AbstractQueuedSynchronizer {
abstract void lock();

//非公平获取,公平锁和非公平锁都需要这个方法
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {   //state == 0表示无锁
        //CAS确保即使有多个线程竞争锁也是安全的
        if (compareAndSetState(0, acquires)) {  //加锁成功
         //当前哪一个线程获取到锁,将线程信息记录到AQS里面  
            setExclusiveOwnerThread(current);   //设置当前持有锁的线程
            return true;                        //获取成功
        }
    } else if (current == getExclusiveOwnerThread()) {//当前线程正是锁持有者
            int nextc = c + acquires;
            if (nextc < 0) // 被锁次数上溢(很少出现)
                throw new Error("Maximum lock count exceeded");
            //锁被持有的情况下,只有持有者才能更新锁保护的资源
            setState(nextc);
            return true;
        }
        return false;
    }
    //释放
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        //只有锁的持有者才能释放锁
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {   //锁被释放
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    //当前线程是否持有锁
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    //锁的持有者
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    //加锁次数
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }
    //是否上锁,根据state字段可以判断
    final boolean isLocked() {
        return getState() != 0;
    }
}

公平性锁实现:FairSync

final static class FairSync extends Sync {
    //见AbstractQueuedSynchronizer.java, 4.2节有
    final void lock() {
        acquire(1);
    }

    //公平版本的tryAcquire,除非是递归调用或没有等待者或者是第一个,否则不授予访问
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //是等待队列的第一个等待者
            if (isFirst(current) &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);   //加锁成功
                return true;
            }
        }
        //当前线程正是线程的持有者
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)  //溢出
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

isFirst的实现,即等待队列为空或者当前线程为等待队列的第一个元素

final boolean isFirst(Thread current) {
    Node h, s;
    return ((h = head) == null ||
            ((s = h.next) != null && s.thread == current) ||
            fullIsFirst(current));
}

非公平性锁实现:NonfairSync

final static class NonfairSync extends Sync {

     // 执行lock,尝试立即闯入,失败就退回常规流程
    final void lock() {
        if (compareAndSetState(0, 1))   //比较并设置state,成功则表示获取成功
            setExclusiveOwnerThread(Thread.currentThread());//锁持有者
        else
            acquire(1);//获取失败,进入常规流程:acquire会首先调用tryAcquire
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

acquire的实现(AbstractQueuedSynchronizer.java)

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

重入锁实现:

  • ReentrantLock都是把具体实现委托给内部类(Sync、NonfairSync、FairSync),
  • ReentrantLock的重入计数是使用AbstractQueuedSynchronizer的state属性的,state大于0表示锁被占用、等于0表示空闲,小于0则是重入次数太多导致溢出了.
  • 可重入锁需要一个重入计数变量,初始值设为0,当成功请求锁时加1,释放锁时减1,当释放锁之后计数为0则真正释放锁;
  • 重入锁还必须持有对锁持有者的引用,用以判断是否可以重入;

condition

synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock同样可以实现该功能,但是要借助于Condition对象。

newCondition方法:

public Condition newCondition()

返回用来与此 Lock 实例一起使用的 Condition 实例。

public interface Condition {
    //使当前线程进入休眠进行等待
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    //唤醒因await进入休眠的一个线程
    void signal();
    //唤醒因await进入休眠的所有线程
    void signalAll();
}

使用await、signal、signalAll是必须加锁的,使用重入锁的加锁释放锁await、signal要通知await的线程必须是作用于同一个Condition实例。

eg:ABC三个线程分别打印各自名称,打印结果ABCABC....A线程通知B线程(ab的Condition)、B线程通知C线程(bc的Condition)、C线程通知A线程(ca的Condition)

public class ABCThread implements Runnable {
    private ReentrantLock lock;
    private Condition sCondition;
    private Condition aCondition;

    public ABCThread(ReentrantLock lock, Condition sCondition, Condition aCondition) {
        this.lock = lock;
        this.sCondition = sCondition;
        this.aCondition = aCondition;
    }

    @Override
    public void run() {
        int i = 0;
        while (i < 10) {
            //加锁
            lock.lock();
            try {
                //接收到C线程通知继续执行,否则就阻塞
                aCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.print(Thread.currentThread().getName()+" ");

            //通知B线程
            sCondition.signal();

            i++;


            //释放锁
            lock.unlock();
        }

    }
}


        ReentrantLock reentrantLock = new ReentrantLock();
        Condition ab = reentrantLock.newCondition();
        Condition bc = reentrantLock.newCondition();
        Condition ca = reentrantLock.newCondition();

        new Thread(new ABCThread(reentrantLock,ab,ca),"A").start();
        new Thread(new ABCThread(reentrantLock,bc,ab),"B").start();
        new Thread(new ABCThread(reentrantLock,ca,bc),"C").start();
        reentrantLock.lock();
        ca.signal();
        reentrantLock.unlock();

Condition与Object中的wati,notify,notifyAll区别:

  1. Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的。
  2. Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,"读线程"需要等待。如果采用Object类中的wait(),notify(),notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程",而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。   但是,通过Condition,就能明确的指定唤醒读线程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Lock锁和ReentrantLock锁 的相关文章

随机推荐

  • 技术领域的面试总结

    在当今互联网中 xff0c 虽然互联网行业从业者众多 xff0c 不断崛起的互联网公司也会很多 xff0c 仍然是很多同学想要进入的企业 那么本篇文章将会为大家很直白的讲解面试流程以及侧重点 仔细阅读本篇文章 xff0c 绝对会有所收获 x
  • Mybatis基于XML与基于注解实现CRUD

    数据库 实体类Student package com pojo Description Created by Resumebb Date 2021 3 26 public class Student 和数据库中的STudent表对应 pri
  • Spring-IOC容器进行对象管理

    目录 IOC概念 IOC思想 Spring管理对象 集成依赖 spring的配置文件 xff08 Applicationcontext xml xff09 创建实体类User Spring对Bean的实例化方式 基于配置形式 1 通过无参构
  • Spring-AOP原理及实现

    Spring AOP AOP Aspect Oriented Programing 面向切面编程 xff1a 扩展功能不通过修改源代码实现 AOP采用横向抽取机制 xff0c 取代传统纵向继承体系实现响应的功能 xff08 性能监控 事务
  • Spring&Mybatis整合及Spring中JDBCTemplate的使用

    Spring和Mybatis整合 在mybatis中 xff0c 操作数据库需要获取到SQLSession对象 xff0c 而该对象的实例过程在mybatis是通过SQLSessionFactoryBuilder读取全局配置文件来实例化一个
  • SpringMVC设计模式

    什么是MVC MVC是模型 Model 视图 View 控制器 Controller 的简写 xff0c 是一种软件设计规范 是将业务逻辑 数据 显示分离的方法来组织代码 MVC主要作用是降低了视图与业务逻辑间的双向偶合 MVC不是一种设计
  • SSM框架整合

    整合思路 主要分为Controller xff0c service层 xff0c dao层 整合dao mybatis和spring的整合 xff0c 通过spring来管理mapper接口 xff0c 数据源 xff0c 使用mapper
  • SSM框架实战-搭建自己的个人博客1-基础架构搭建

    前言 本系列文章主要通过从零开始搭建自己的个人博客 xff0c 来加深对SSM框架的学习与使用 xff0c 了解一个系统从提出到设计 到开发 到测试 部署运行的过程 xff0c 并记录在搭建过程中的学习心得 遇见的错误及解决方式 代码放在g
  • SSM框架实战-搭建自己的个人博客2-UEditor编辑器的使用

    目录 UEditor 博客内容提交与展示功能测试 Controller开发 新增博客页面add ueditor jsp 博客详情界面detail jsp 博客新增和展示详情功能开发 博客存储 博客标题开发 标签POJO类 TagMapper
  • SSM框架实战-搭建自己的个人博客3-登录实现及前端界面设计

    目录 后台登录功能 前端页面 后端开发 前端界面设计 详情首页 js脚本 SSM整体设计 Dao层 Service层 Mapper xml Controller 子博文界面 部署至服务器 后台登录功能 登录页面 xff1a 用户名和密码 通
  • 超分辨率重建-PNSR与SSIM的计算(RGB、YUV和YCbCr互转)

    RGB YUV和YCbCr 自己复现的网络跑出来的模型进行预测的时候 xff0c 不知道为啥算出来的结果比论文中要低好多 不论scale factor为多少 xff0c 我算出来的结果均普遍低于论文中给出的 xff0c PSNR大概低个1
  • 如何写简历

    注意点 xff1a 篇幅 校招一页 社招二页 谨慎使用精通 精通 gt 熟悉 xff08 推荐使用 xff09 gt 掌握 xff08 推荐使用 xff09 gt 了解 xff08 推荐使用 xff09 拿不准的不要写在简历上 突出自己技能
  • SSM框架实战-搭建自己的个人博客4-文章管理与展示

    实现功能 主要实现上图所示的功能 xff0c 从数据库中查询到所有文章数据 xff0c 并进行显示如标题 xff0c 栏目等信息 xff0c 可以通过分类查询文章 xff0c 通过标签查询文章 xff0c 也可以通过搜索进行模糊查询 xff
  • Pytorch加载与保存模型(利用pth的参数更新h5预训练模型)

    前言 以前用Keras用惯了 xff0c fit和fit generator真的太好使了 xff0c 模型断电保存搞个checkpoint回调函数就行了 近期使用pytorch进行训练 xff0c 苦于没有类似的回调函数 xff0c 写完网
  • 如何用pyplot优雅的绘制loss,acc,miou,psnr变化曲线

    前言 TensorFlowBoard过于强大 xff0c 导致我对它依赖性很强 xff0c 今年转手使用pytorch进行开发 xff0c 本以为没了TensorFlowBoard xff0c 后来发现人家Tensorflow封装了个Ten
  • Pytorch实现CA,SA,SE注意力机制

    通道注意力CA class ChannelAttention nn Module def init self in planes ratio 61 16 super ChannelAttention self init self avg p
  • Python使用OpenCV按自定义帧率提取视频帧并保存

    在做室外语义分割 视觉导航与定位的时候 xff0c 通常会用对一个连续的视频帧进行测试 xff0c 除去常用数据集外 xff0c 也经常会自己制作一些数据集 xff0c 这个工具类可以按需求对视频进行分帧提取 xff0c 封装好了直接可以使
  • 悲观锁与乐观锁详解

    悲观锁 悲观锁顾名思义是从悲观的角度去思考问题 xff0c 解决问题 它总是会假设当前情况是最坏的情况 xff0c 在每次去拿数据的时候 xff0c 都会认为数据会被别人改变 xff0c 因此在每次进行拿数据操作的时候都会加锁 xff0c
  • 亚像素卷积网络(ESPCN)学习与Pytorch复现

    论文内容 论文地址 xff1a Real Time Single Image and Video Super Resolution Using an Efficient Sub Pixel Convolutional Neural Netw
  • Lock锁和ReentrantLock锁

    前言 JDK 1 5中提供的锁的接口java util concurrent locks Lock xff0c 其提供了一些ReentrantLock ReentrantReadWriteLock实现类 参考JDK文档 xff1a Java