JUC并发编程第十三章——读写锁、邮戳锁

本章路线总纲

无锁——>独占锁——>读写锁——>邮戳锁

1 关于锁的面试题

  • 你知道Java里面有那些锁
  • 你说说你用过的锁,锁饥饿问题是什么?
  • 有没有比读写锁更快的锁
  • StampedLock知道吗?(邮戳锁/票据锁)
  • ReentrantReadWriteLock有锁降级机制,你知道吗?

2 简单聊聊ReentrantReadWriteLock

类图:

读写锁的演变情况:

2.1 是什么?

读写锁说明

  • 一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程

演变

  • 无锁无序->加锁->读写锁->邮戳锁

读写锁意义和特点

  • 读写锁只允许读读共存,而读写和写写依然是互斥的,恰好大多实际场景是”读/读“线程间不存在互斥关系,只有”读/写“线程或者”写/写“线程间的操作是需要互斥的,因此引入了 ReentrantReadWriteLock
  • 一个ReentrantReadWriteLock同时只能存在一个写锁但是可以存在多个读锁,但是不能同时存在写锁和读锁,也即资源可以被多个读操作访问,或一个写操作访问,但两者不能同时进行。
  • 只有在读多写少情景之下,读写锁才具有较高的性能体现。

2.2 特点

可重入、读写兼顾

结论:一体两面,读写互斥,读读共享,读没有完成的时候其他线程写锁无法获得

ReentrantReadWriteLock的缺点:

1. 锁饥饿问题:

  • ReentrantReadWriteLock实现了读写分离,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,因此当前有可能会一直存在读锁,而无法获得写锁。

2. 锁降级:

  • 将写锁降级为读锁------>遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级为读锁
  • 如果一个线程持有了写锁,在没有释放写锁的情况下,它还可以继续获得读锁。这就是写锁的降级,降级成为了读锁。
  • 如果释放了写锁,那么就完全转换为读锁
  • 如果有线程在读,那么写线程是无法获取写锁的,是悲观锁的策略

2.3 读写锁案例

  • 使用读写锁之前,使用synchronized的情况
public class ReentrantReadWriteLockDemo {public static void main(String[] args) {MyCache cache = new MyCache();//开启10个线程,写入数据for (int i = 1; i <= 10; i++) {int finalI = i;new Thread(() -> {cache.write(finalI + "", finalI + "");}, String.valueOf(i)).start();}//开启10个线程,读取数据for (int i = 1; i <= 10; i++) {int finalI = i;new Thread(() -> {cache.read(finalI + "");}, String.valueOf(i)).start();}}
}//模拟一个缓存资源类,有读写两种功能
class MyCache {HashMap<String, String> map = new HashMap<>();ReentrantLock lock = new ReentrantLock();//读写都加锁public void write(String key, String value) {lock.lock();try {System.out.println(Thread.currentThread().getName() + "线程开始写入数据...");//延迟500ms模拟业务耗时,同时可以看出读写不能共同执行 (因为运行结果是先打印一个线程写入,再打印对应线程写入完成)TimeUnit.MILLISECONDS.sleep(500);map.put(key, value);System.out.println(Thread.currentThread().getName() + "线程完成写入数据!");} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}}public void read(String key) {lock.lock();try {System.out.println(Thread.currentThread().getName() + "线程开始读取数据...");String val = map.get(key);TimeUnit.MILLISECONDS.sleep(200);System.out.println(Thread.currentThread().getName() + "线程读取到的数据是:\t" + val);} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}}
}
运行结果:
1线程开始写入数据...
1线程完成写入数据!
2线程开始写入数据...
2线程完成写入数据!
3线程开始写入数据...
3线程完成写入数据!
4线程开始写入数据...
4线程完成写入数据!
5线程开始写入数据...
5线程完成写入数据!
6线程开始写入数据...
6线程完成写入数据!
7线程开始写入数据...
7线程完成写入数据!
9线程开始写入数据...
9线程完成写入数据!
8线程开始写入数据...
8线程完成写入数据!
10线程开始写入数据...
10线程完成写入数据!
1线程开始读取数据...
1线程读取到的数据是:	1
2线程开始读取数据...
2线程读取到的数据是:	2
3线程开始读取数据...
3线程读取到的数据是:	3
4线程开始读取数据...
4线程读取到的数据是:	4
5线程开始读取数据...
5线程读取到的数据是:	5
6线程开始读取数据...
6线程读取到的数据是:	6
7线程开始读取数据...
7线程读取到的数据是:	7
8线程开始读取数据...
8线程读取到的数据是:	8
9线程开始读取数据...
9线程读取到的数据是:	9
10线程开始读取数据...
10线程读取到的数据是:	10

说明:可以看出,开始写入/读取和完成写入/读取,都是成对出现的。这说明这写入/读取期间,其他线程不能执行写入/读取。读写/读读/写写都互斥了。

问题:我们希望的情况应该是,读写/写写都互斥,但读读可以并发读取。从而引出了读写锁(对写独占,对读共享)

  • 使用读写锁
public class ReentrantReadWriteLockDemo {public static void main(String[] args) {MyCache cache = new MyCache();//开启10个线程,写入数据for (int i = 1; i <= 10; i++) {int finalI = i;new Thread(() -> {cache.write(finalI + "", finalI + "");}, String.valueOf(i)).start();}//开启10个线程,读取数据for (int i = 1; i <= 10; i++) {int finalI = i;new Thread(() -> {cache.read(finalI + "");}, String.valueOf(i)).start();}}
}//模拟一个缓存资源类,有读写两种功能
class MyCache {HashMap<String, String> map = new HashMap<>();ReentrantLock lock = new ReentrantLock();ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();//读写都加锁public void write(String key, String value) {rwLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + "线程开始写入数据...");//延迟500ms模拟业务耗时,同时可以看出读写不能共同执行 (因为运行结果是先打印一个线程写入,再打印对应线程写入完成)TimeUnit.MILLISECONDS.sleep(500);map.put(key, value);System.out.println(Thread.currentThread().getName() + "线程完成写入数据!");} catch (InterruptedException e) {e.printStackTrace();} finally {rwLock.writeLock().unlock();}}public void read(String key) {rwLock.readLock().lock();try {System.out.println(Thread.currentThread().getName() + "线程开始读取数据...");String val = map.get(key);TimeUnit.MILLISECONDS.sleep(200);System.out.println(Thread.currentThread().getName() + "线程读取到的数据是:\t" + val);} catch (InterruptedException e) {e.printStackTrace();} finally {rwLock.readLock().unlock();}}
}
运行结果:
1线程开始写入数据...
1线程完成写入数据!
2线程开始写入数据...
2线程完成写入数据!
3线程开始写入数据...
3线程完成写入数据!
4线程开始写入数据...
4线程完成写入数据!
5线程开始写入数据...
5线程完成写入数据!
6线程开始写入数据...
6线程完成写入数据!
7线程开始写入数据...
7线程完成写入数据!
8线程开始写入数据...
8线程完成写入数据!
9线程开始写入数据...
9线程完成写入数据!
10线程开始写入数据...
10线程完成写入数据!
1线程开始读取数据...
9线程开始读取数据...
7线程开始读取数据...
6线程开始读取数据...
5线程开始读取数据...
3线程开始读取数据...
4线程开始读取数据...
2线程开始读取数据...
10线程开始读取数据...
8线程开始读取数据...
10线程读取到的数据是:10
4线程读取到的数据是:	4
2线程读取到的数据是:	2
8线程读取到的数据是:	8
3线程读取到的数据是:	3
7线程读取到的数据是:	7
6线程读取到的数据是:	6
5线程读取到的数据是:	5
1线程读取到的数据是:	1
9线程读取到的数据是:	9

说明:可以看出,所有写操作还是跟之前一样,全部互斥。但读操作可以并发读取。

结论

使用ReadWriteLock实现读写操作,一体两面,读写互斥,读读共享,但是读没有完成时候其它线程写锁无法获取


2.4 锁降级

ReentrantReadwriteLock锁降级:

  • 将写入锁降级为读锁(类似Linux文件读写权限理解,就像写权限要高于读权限一样),锁的严苛程度变强叫做升级,反之叫做降级。

ReentrantReadwriteLock的特性:

写锁降级成为读锁

  1. 如果同一个线程持有了写锁,在没有释放写锁的情况下,它还可以继续获得读锁。这就是写锁的降级,降级成为了读锁。
  2. 规则惯例,先获取写锁,然后获取读锁,再释放写锁的次序。
  3. 如果释放了写锁,那么就完全转换为读锁。

总之:

  • 如果一个线程先获取写锁,在获取写锁和释放写锁之间可以再获取读锁,如果获取了读锁,之前获取的写锁且被释放了。那么之前的写锁,就降级为现在的读锁了。

why?要有这么个特性?

----后面解释,大概目的:锁降级是为了让当前线程感知到数据的变化,目的是保证数据的可见性


2.5 写锁可以降级为读锁,但读锁不可升级为写锁

重入还允许通过获取写入锁定,然后读取锁然后释放写锁从写锁到读取锁,但是从读锁升级到写锁是不可能的

锁降级的目的:锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性

样例1

锁降级:获取写锁 ——> 获取读锁 ——> 释放写锁 ——> 释放读锁      ✔ 可以完成

import java.util.concurrent.locks.ReentrantReadWriteLock;public class LockDownGradingDemo {public static void main(String[] args) {ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();// 例一:正常两个A、B线程
//        new Thread(() -> {
//            readLock.lock();
//            System.out.println("---A线程读取---");
//            readLock.unlock();
//        }, "A").start();
//
//        new Thread(() -> {
//            writeLock.lock();
//            System.out.println("---B线程写入---");
//            writeLock.unlock();
//        }, "B").start();// 例二:only one 同一个线程writeLock.lock();System.out.println("---写入---");// 一些其它的业务操作...readLock.lock();System.out.println("---读取---");// 一些其它的业务操作...writeLock.unlock();readLock.unlock();}}输出结果:
---写入---
---读取---

说明: 

  • 同一个线程的写后立刻读是可以的,即将写入锁降级为读锁是支持的,这种就是锁降级

样例2

锁降级:获取读锁 ——> 获取写锁 ——> 释放读锁 ——> 释放写锁      X 不可以完成

import java.util.concurrent.locks.ReentrantReadWriteLock;public class LockDownGradingDemo2 {public static void main(String[] args) {ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();// 例二:only one 同一个线程readLock.lock();System.out.println("---读取---");// 一些其它的业务操作...writeLock.lock();System.out.println("---写入---");// 一些其它的业务操作...readLock.unlock(); // 这个位置和下面那个位置效果一样writeLock.unlock();
//        readLock.unlock();}}输出结果:
---读取---
// ...程序未结束

说明:

  • 如果有线程读没有完成的时候,写线程无法获取锁,必须要等着读锁释放所锁后才有机会写,这是悲观锁的策略

1、2例子对比小结:

  • 其实想想很容易理解:同一个线程,先读,还没有读完(读锁readLock没有unlock),我又去写。那么我之前的不就是脏数据了?因此应该先全部读完,才能执行写操作。
  • 而例子1中,先写,就算没写完(写锁没有释放),我立马去读,由于读操作不会导致数据不一致。因此,这是合理的。


2.6 写锁和读锁是互斥的

        写锁和读锁是互斥的(这里的互斥是指线程间的互斥,当前线程可以获取到写锁又获取到读锁,但是获取到了读锁不能继续获取写锁),这是因为读写锁要保持写操作的可见性。因为,如果允许读锁在被获取的情况下对写锁的获取,那么正在运行的其他读线程无法感知到当前写线程的操作

因此,分析读写锁ReentrantReadWriteLock,会发现它有个潜在的问题:

  • 即ReentrantReadWriteLock读的过程中不允许写,只有等待线程都释放了读锁,当前线程才能获取写锁也就是写入必须等待,这是一种悲观的读锁,人家还在读着那,你先别去写,省的数据乱。

2.7 Oracle公司ReentrantReadWriteLock使用样例

* <p><b>Sample usages</b>. Here is a code sketch showing how to perform
* lock downgrading after updating a cache (exception handling is
* particularly tricky when handling multiple locks in a non-nested
* fashion):
*
* <pre> {@code
* class CachedData {
*   Object data;
*   volatile boolean cacheValid;
*   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
*
*   void processCachedData() {
*     rwl.readLock().lock();// 1
*     if (!cacheValid) {
*       // Must release read lock before acquiring write lock
*       rwl.readLock().unlock();// 2
*       rwl.writeLock().lock();// 3
*       try {
*         // Recheck state because another thread might have
*         // acquired write lock and changed state before we did.
*         if (!cacheValid) {
*           data = ...//在此做一些写操作
*           cacheValid = true;
*         }
*         // Downgrade by acquiring read lock before releasing write lock
*         rwl.readLock().lock();// 4
*       } finally {
*         rwl.writeLock().unlock(); // 5 Unlock write, still hold read
*       }
*     }
*
*     try {
*       use(data);
*     } finally {
*       rwl.readLock().unlock();// 6
*     }
*   }
* }}</pre>

代码解读:

  • 1-6 六个加锁/释放锁的操作。1-2对应读锁、3-5对应写锁、4-6对应读锁。volatile类型的cacheValid变量,保证其可见性
  • 首先,线程第一次进来,资源类CacheData是没有被修改过的。先加读锁1,if判断 ( !cacheValid ) 的值为true。在2的位置释放读锁。
  • 接着准备写操作,先获取写锁3。并进行双端检索 (防止其它线程恰好修改了)。做完写操作后,把cacheValid改为true。为了立刻读取到我刚刚修改的数据data,必须发生锁降级,在释放写锁5之前获取读锁4。原因:如果我先把写锁释放了,再获取读锁,出现了没有锁的空档期。在此期间锁可能被其他线程获取并修改数据,无法保证读锁立马能被同一个线程获取,可能在我使用data数据的期间,data数据又被修改了!
  • 在4的位置已经获取了读锁,代码运行到5的位置释放写锁。发生锁降级。之后在use(data)这行使用刚刚修改的data数据,最后在6位置释放读锁。让其他线程继续抢锁。

        这里只有锁降级才能保证,同一个线程我先执行写操作,再继续读我刚刚写的数据。在整个线程执行业务的过程中,一直是加锁(不是写锁就是读锁)状态,没有出现空档期,因此整个操作保证了原子性。

如果违背锁降级的步骤,如果违背锁降级的步骤, 如果违背锁降级的步骤

  • 如果当前的线程C在修改完cache中的数据后,没有获取读锁而是直接释放了写锁,那么假设此时另一个线程D获取了写锁并修改了数据,那么C线程无法感知到数据已被修改,则数据出现错误。

4 邮戳锁StampedLock

4.1 是什么?

StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化

stamp 代表了锁的状态。当stamp返回零时,表示线程获取锁失败,并且当释放锁或者转换锁的时候,都要传入最初获取的stamp值。

4.2 它是由饥饿问题引出

锁饥饿问题:

  • ReentrantReadWriteLock实现了读写分离,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,因此当前有可能会一直存在读锁,而无法获得写锁。

如何解决锁饥饿问题:

  • 使用”公平“策略可以一定程度上缓解这个问题
  • 使用”公平“策略是以牺牲系统吞吐量为代价的
  • StampedLock类的乐观读锁方式--->采取乐观获取锁,其他线程尝试获取写锁时不会被阻塞,在获取乐观读锁后,还需要对结果进行校验

4.3 StampedLock的特点

  • 所有获取锁的方法,都返回一个邮戳,stamp为零表示失败,其余都表示成功
  • 所有释放锁的方法,都需要一个邮戳,这个stamp必须是和成功获取锁时得到的stamp一致
  • StampedLock是不可重入的危险(如果一个线程已经持有了写锁,在去获取写锁的话会造成死锁)
  • StampedLock有三种访问模式:
    • Reading(悲观读模式):功能和ReentrantReadWriteLock的读锁类似
    • Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
    • Optimistic reading(乐观读模式):无锁机制,类似与数据库中的乐观锁,支持读写并发,很乐观认为读时没人修改,假如被修改在实现升级为悲观读模式
  • 一句话:读的过程中也允许写锁介入

4.4 邮戳锁StampedLock代码演示

4.4.1 传统的读写锁模式——读的时候写锁不能获取

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;public class StampedLockDemo {static int number = 37;static StampedLock stampedLock = new StampedLock();public void write() {long stamp = stampedLock.writeLock();System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改");try {number = number + 13;} finally {stampedLock.unlockWrite(stamp);}System.out.println(Thread.currentThread().getName() + "\t" + "写线程结束修改");}// 悲观锁,读没有完成时候写锁无法获得锁public void read() {long stamp = stampedLock.readLock();System.out.println(Thread.currentThread().getName() + "\t" + "come in readLock code Block, 4 second continue...");for (int i = 0; i < 4; i++) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t" + "正在读取中");}try {int result = number;System.out.println(Thread.currentThread().getName() + "\t" + "获得成员变量值result: " + result);System.out.println("写线程没有修改成功,读锁时候写锁无法介入,传统的读写互斥");} finally {stampedLock.unlockRead(stamp);}}public static void main(String[] args) {StampedLockDemo resource = new StampedLockDemo();new Thread(() -> resource.read(), "readThread").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t" + "come in");resource.write();}, "writeThread").start();try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t" + "number:" + number);}}输出结果:
readThread	come in readLock code Block, 4 second continue...
readThread	正在读取中
writeThread	come in
readThread	正在读取中
readThread	正在读取中
readThread	正在读取中
readThread	获得成员变量值result: 37
写线程没有修改成功,读锁时候写锁无法介入,传统的读写互斥
writeThread	写线程准备修改
writeThread	写线程结束修改
main	number:50

这和之前的读写锁ReentrantLock使用类似,但邮戳锁不可重入


4.4.2 乐观读模式——读的过程中也允许写锁介入

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;public class StampedLockDemo2 {static int number = 37;static StampedLock stampedLock = new StampedLock();public void write() {long stamp = stampedLock.writeLock();System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改");try {number = number + 13;} finally {stampedLock.unlockWrite(stamp);}System.out.println(Thread.currentThread().getName() + "\t" + "写线程结束修改");}// 乐观读,读的过程中也允许写锁介入public void read() {long stamp = stampedLock.tryOptimisticRead();int result = number;// public boolean validate(long stamp)方法官方解释// 如果自发出给定标记后未完全获取锁,则返回true。如果标记为零,则始终返回false。如果图章代表当前持有的锁,则始终返回true。 使用未从tryOptimisticRead()获取的值或此锁定的锁定方法调用此方法没有定义的效果或结果System.out.println("4秒前 stampedLock.validate方法值(true 无修改 false有修改)" + "\t" + stampedLock.validate(stamp));// 故意间隔4秒钟,很乐观认为读取中没有其它线程修改过number值,具体靠判断for (int i = 0; i < 4; i++) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t" + " 正在读取...." + i + "秒后,stampedLock.validate方法值(true 无修改 false有修改)" + "\t" + stampedLock.validate(stamp));}if (!stampedLock.validate(stamp)) {System.out.println("有人修改----------有写操作");stamp = stampedLock.readLock();try {System.out.println("从乐观读升级为悲观读");result = number;System.out.println("重新悲观读后result:" + result);} finally {stampedLock.unlockRead(stamp);}}System.out.println(Thread.currentThread().getName() + "\t" + "finally value: " + result);}public static void main(String[] args) {StampedLockDemo2 resource = new StampedLockDemo2();new Thread(resource::read, "readThread").start();// 暂停2秒线程,演示读过程可以写介入// 如果暂停>4秒线程,这样读过程没有写介入,此时输出finally value:37try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t" + " come in");resource.write();}, "writeThread").start();}}输出结果:
4秒前 stampedLock.validate方法值(true 无修改 false有修改)	true
readThread	 正在读取....0秒后,stampedLock.validate方法值(true 无修改 false有修改)	true
writeThread	 come in
writeThread	写线程准备修改
writeThread	写线程结束修改
readThread	 正在读取....1秒后,stampedLock.validate方法值(true 无修改 false有修改)	false
readThread	 正在读取....2秒后,stampedLock.validate方法值(true 无修改 false有修改)	false
readThread	 正在读取....3秒后,stampedLock.validate方法值(true 无修改 false有修改)	false
有人修改----------有写操作
从乐观读升级为悲观读
重新悲观读后result:50
readThread	finally value: 50

4.5 StampedLock的缺点

  • StampedLock不支持重入,没有Re开头
  • StampedLock的悲观读锁和写锁都不支持条件变量,这个也需要主要
  • 使用StampedLock一定不要调用中断操作,即不要调用interrupt()方法

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

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

相关文章

使用自定义注解进行权限校验

一&#xff0c;前言 对于一些重复性的操作我们可以用提取为util的方式进行处理&#xff0c;但也可以更简便一些&#xff0c;比如自定义个注解进行。选择看这篇文章的小伙伴想必都对注解不陌生&#xff0c;但是可能对它的工作原理不太清楚。这里我们用注解实现对接口的权限校验…

Wireshark v4 修改版安装教程(免费开源的网络嗅探抓包工具)

前言 Wireshark&#xff08;前称Ethereal&#xff09;是一款免费开源的网络嗅探抓包工具&#xff0c;世界上最流行的网络协议分析器&#xff01;网络封包分析软件的功能是撷取网络封包&#xff0c;并尽可能显示出最为详细的网络封包资料。Wireshark网络抓包工具使用WinPCAP作为…

基于GWO-CNN-LSTM数据时间序列预测(多输入单输出)-多维时间序列模型-MATLAB实现

基于GWO-CNN-LSTM数据时间序列预测(多输入单输出)-多维时间序列模型-MATLAB实现 基于灰狼优化&#xff08;Grey Wolf Optimizer, GWO&#xff09;、卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09;和长短期记忆网络&#xff08;Long Short-Term Memor…

【计算机视觉(11)】

基于Python的OpenCV基础入门——图像梯度变换 图像梯度变换Sobel算子Scharr算子Laplacian算子 图像梯度变换的代码实现以及效果图 图像梯度变换 图像梯度变换可以用于边缘检测、特征提取、增强图像和压缩图像等多种任务。图图像梯度可以把图像看成二维离散函数&#xff0c;图像…

什么是进程?

目录 进程 进程的特征, 概念 我们下面先简单介绍一下什么是进程 接下来看看一个程序的运行过程 进程的组成 进程的状态和转换 进程的状态 进程状态的转换 ​编辑 进程的组织方式 进程控制 如何实现进程控制 为什么进程控制的过程需要一气呵成? 进程控制的实现…

前端初学java

目录 java术语 JDK Javac Java Jdb Jhat JVM JRE JAR JDK下载 运行java文件 字面量 隐式转换 强制转换 注意 运算符 &&、||、&、| Switch 程序入口 String[] args 数组 静态初始化 动态初始化 变量初始化 Java内存 方法 重载 Final 包 …

智警杯数据库学习(1)

CentOS中安装MySQL数据库 检测系统是否自带安装 MySQL 首先检查是否自带mysql rpm -qa | grep mysql 如果有删除 rpm -e mysq 未安装&#xff0c;开始安装 进入software目录&#xff0c;解压安装包mysql5.7.25 cd /root/software tar -xvf mysql-5.7.25-1.el7.x86_64.rp…

【决战欧洲杯巅峰】欧洲杯含金量比世界杯高吗?有走地数据分析软件吗?

关于欧洲杯和世界杯的含金量对比&#xff0c;这是一个相当主观的问题&#xff0c;因为两者的价值和重要性很大程度上取决于个人的喜好和观点。但我可以从一些关键方面来为你提供比较的视角。 首先&#xff0c;从参赛队伍和竞技水平来看&#xff0c;世界杯无疑是全球范围内最具…

[渗透测试学习] SolarLab-HackTheBox

SolarLab-HackTheBox 信息搜集 nmap扫描端口 nmap -sV -v 10.10.11.16扫描结果如下 PORT STATE SERVICE VERSION 80/tcp open http nginx 1.24.0 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows n…

C/S、B/S架构(详解)

一、CS、BS架构定义 CS架构&#xff08;Client-Server Architecture&#xff09;是一种分布式计算模型&#xff0c;其中客户端和服务器之间通过网络进行通信。在这种架构中&#xff0c;客户端负责向服务器发送请求&#xff0c;并接收服务器返回的响应。服务器则负责处理客户端的…

浅谈RC4

一、什么叫RC4&#xff1f;优点和缺点 RC4是对称密码&#xff08;加密解密使用同一个密钥&#xff09;算法中的流密码&#xff08;一个字节一个字节的进行加密&#xff09;加密算法。 优点&#xff1a;简单、灵活、作用范围广&#xff0c;速度快 缺点&#xff1a;安全性能较差&…

Pytorch编写Transformer

本文参考自https://github.com/datawhalechina/learn-nlp-with-transformers/blob/main/docs/ 在学习了图解Transformer以后&#xff0c;需要用Pytorch编写Transformer&#xff0c;下面是写代码的过程中的总结&#xff0c;结构根据图解Transformer进行说明。 import numpy as …

前字节员工自爆:我原腾讯一哥们,跳槽去小公司做小领导,就签了竞业,又从小公司离职去了对手公司,结果被发现了,小公司要他赔80万

“世界那么大&#xff0c;我想去看看”&#xff0c;这句曾经火遍网络的辞职宣言&#xff0c;说出了多少职场人心中的渴望。然而&#xff0c;当我们真的迈出跳槽那一步时&#xff0c;才发现&#xff0c;现实远比想象中残酷得多。 最近&#xff0c;一起前字节跳动员工爆料的事件…

年终奖发放没几天,提离职领导指责我不厚道,我该怎么办?

“年终奖都发了&#xff0c;你还跳槽&#xff1f;太不厚道了吧&#xff01;” “拿完年终奖就走人&#xff0c;这不是典型的‘骑驴找马’吗&#xff1f;” 每到岁末年初&#xff0c;关于“拿到年终奖后是否应该立即辞职”的话题总会引发热议。支持者认为&#xff0c;这是个人…

如何验证Rust中的字符串变量在超出作用域时自动释放内存?

讲动人的故事,写懂人的代码 在公司内部的Rust培训课上,讲师贾克强比较了 Rust、Java 和 C++ 三种编程语言在变量越过作用域时自动释放堆内存的不同特性。 Rust 通过所有权系统和借用检查,实现了内存安全和自动管理,从而避免了大部分内存泄漏。Rust 自动管理标准库中数据类…

PID控制算法学习笔记分享

目录 一、参数设定 二、PID计算公式 三、位置式PID代码实现 四、增量式PID代码实现 五、两种控制算法的优缺点 六、PID算法的改进 一、参数设定 比例系数&#xff08;kp&#xff09;&#xff1a;P项的作用是根据当前误差的大小来产生一个控制量。它直接与误差成正比&#…

【机器学习300问】126、词嵌入(Word Embedding)是什么意思?

人类的文字&#xff0c;作为一种高度抽象化的符号系统&#xff0c;承载着丰富而复杂的信息。为了让电脑也能像人类一样理解并处理这些文字&#xff0c;科学家们不断探索各种方法&#xff0c;以期将人类的语言转化为计算机能够理解的格式。 一、One-Hot编码的不足 在自然语言处…

NSSCTF中的[WUSTCTF 2020]朴实无华、[FSCTF 2023]源码!启动! 、[LitCTF 2023]Flag点击就送! 以及相关知识点

目录 [WUSTCTF 2020]朴实无华 [FSCTF 2023]源码&#xff01;启动! [LitCTF 2023]Flag点击就送&#xff01; 相关知识点 1.intval 绕过 绕过的方式&#xff1a; 2.session伪造攻击 [WUSTCTF 2020]朴实无华 1.进入页面几乎没什么可用的信息&#xff0c;所以想到使用dis…

Spring MVC学习记录(基础)

目录 1.SpringMVC概述1.1 MVC介绍1.2 Spring MVC介绍1.3 Spring MVC 的核心组件1.4 SpringMVC 工作原理 2.Spring MVC入门2.1 入门案例2.2 总结 3.RequestMapping注解4.controller方法返回值4.1 返回ModelAndView4.2 返回字符串4.2.1 逻辑视图名4.2.2 Redirect重定向4.2.3 forw…

Shopee菲律宾本土店允许中途无理由退货,如何应对退货后库存混乱问题?

Shopee菲律宾本土店最近实施了一项新政策&#xff0c;自2024年6月10日起&#xff0c;允许买家在商品仍在运输途中申请退货与退款&#xff0c;此即“在途退货/退款”功能&#xff0c;主要的目的是为了提升买家的购物体验&#xff0c;增强市场竞争力。 图源&#xff1a;Shopee菲律…