JUC并发编程之线程锁(一)
目录
1.ReentrantLock(互斥锁)
2.ReentRantReaderWriterLock(互斥读写锁)
3.StampedLock(无障碍锁)
4.Condition(自定义锁)
5.LockSupport
问题引出:
由于传统的线程控制需要用到同步机制Synchronized与 Object类中的wait();notify();函数进行控制,但是这样控制并不容易,所以在JUC中提供了全新的框架,
框架核心接口为:1.Lock();2.ReaderWriteLock();
1.ReentrantLock(互斥锁)
ReentrantLock是一种互斥锁,意思是一旦有一个线程获取到锁,那么其他线程就无法运行将会进行等待阻塞。其中又分为公平锁和非公平锁。区别在于获取锁的机制是否公平。并且该锁通过一个FIFO队列管理所有的等待线程。
以下是ReentrantLock类的常用方法:
方法名 | 描述 |
---|---|
ReentrantLock() | 创建一个新的ReentrantLock实例。 |
ReentrantLock(boolean fair) | 创建一个新的ReentrantLock实例,根据参数fair的值决定是否按公平顺序获取锁。 |
lock() | 获取锁,如果锁不可用,则当前线程将被阻塞,直到锁可用。 |
unlock() | 释放锁,使得其他等待锁的线程可以尝试获取锁。 |
tryLock() | 尝试获取锁,如果锁可用则获取锁并返回true,否则立即返回false。 |
isFair() | 判断锁是否是公平锁,如果是公平锁则返回true,否则返回false。 |
new Condition() | 创建一个与该锁关联的Condition对象,用于线程间等待和通知,在调用Condition的await方法时,当前线程会释放锁。 |
案例:有五个售票员同时售卖8张票
package Example2110;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;class Ticeket{private int ticket = 8;
// 创建互斥锁并设置为公平锁,所有线程运行都是等概率的ReentrantLock reentrantLock = new ReentrantLock(true);public void sale(){while (true){// 开启锁只有一个线程能通过reentrantLock.lock();try {
// 模拟网络延迟TimeUnit.SECONDS.sleep(1);if (ticket>0){System.out.println(Thread.currentThread().getName()+"售卖第"+ticket--+"张票");}else {System.out.println("卖完咯");break;}}catch (Exception e){e.printStackTrace();}finally {
// 释放锁reentrantLock.unlock();
// 线程让行,让其他线程也有机会运行Thread.yield();}}}
}
public class javaDemo {public static void main(String[] args) {Ticeket ticket = new Ticeket();
// 创建多个售卖对象for (int i=1;i<=5;i++){new Thread(()->{ticket.sale();},"售票员"+i).start();}}
}
问题引出:
独占锁的最大弊端就在于其阻断了其他线程只允许一个线程工作。在很多情况下就会造成性能问题。所以引入了ReentranReadWritetLock(读写互斥锁)
面试题:ReentrantLock 是如何实现可重入性的?
(1)什么是可重入性
一个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;而这个线程尝试获取自己持有锁时,如果成功说明该锁是可重入的,反之则不可重入。
(2)synchronized是如何实现可重入性
synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以获取该锁并执行相应的方法。根据虚拟机规范要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了对象的锁,把锁的计数器+1,相应的在执行monitorexit指令后锁计数器-1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
(3)ReentrantLock如何实现可重入性
ReentrantLock使用内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(非公公平锁)和FairSync(公平锁)。Sync通过继承AQS实现,在AQS中维护了一个private volatile int state来计算重入次数,避免频繁的持有释放操作带来的线程问题。
(4)代码分析
当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以尝试获取锁。
当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。
2.ReentRantReaderWriterLock(互斥读写锁)
这个锁的机制和ReentrantLock区别并不大,但是将其权力拆分出来,分别为共享锁读,和互斥锁写锁。意思就是多线程想要修改数据就只允许其中一个线程修改其他等待,但是读取数据大家都可以随意读取。
ReentrantReaderWriter类的常用操作方法:
方法名 | 描述 |
---|---|
readLock() | 获取读锁,如果写锁被占用,则当前线程会被阻塞,直到写锁释放。 |
writeLock() | 获取写锁,如果读锁或写锁被占用,则当前线程会被阻塞,直到所有的读锁和写锁都释放。 |
注意:实例化该对象用到开头提到框架中的ReadWriteLock接口进行向上转型得到对象。
如:ReaderWriteLock readWriterLock = new RenntrantReaderWriterLock();
以下案例通过ReentrantReadWriteLock实现一个银行10个ATM机,五个进行存款,五个进行读取账户信息
package Example2111;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;class Bank{private String name = "黄田";private double account = 0;private ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
// 存钱public void write(double money){
// 设置写锁并开启this.readWriteLock.writeLock().lock();try {account = account+money;
// 模拟网络延迟TimeUnit.SECONDS.sleep(1);System.out.println("您正在使用"+Thread.currentThread().getName()+"存入存款:"+money+",当前账户余额为"+account);}catch (Exception e){e.printStackTrace();}finally {
// 更新完数据就可以解放写锁this.readWriteLock.writeLock().unlock();}}
// 读取存款public void Read(){
// 设置读取共享锁this.readWriteLock.readLock().lock();try {
// 模拟网络延迟TimeUnit.SECONDS.sleep(2);System.out.println("您正在使用"+Thread.currentThread().getName()+"读取账户信息:"+":当前账户人名称"+name+",账户人当前的余额为"+account);}catch (Exception e){e.printStackTrace();}finally {
// 读取完数据自动解锁this.readWriteLock.readLock().unlock();}}
}public class javaDemo {public static void main(String[] args) {Bank bank = new Bank();double moneys[]=new double[]{10,500,300,400};
// 五个ATM存款for (int i =1;i<=5;i++){new Thread(()->{for (int j=0;j<moneys.length;j++){bank.write(moneys[j]);}},"银行分行ATM"+i+"号取款机").start();}
// 五个ATM读取存款for (int i=5;i<=10;i++){new Thread(()->{bank.Read();},"银行分行ATM"+i).start();}}
}
面试题:synchronized 和 ReentrantLock 区别是什么?
synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量
synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。
相同点:两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
主要区别如下:
ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。
二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,synchronized 操作的应该是对象头中 mark wordJava中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
3.StampedLock(无障碍锁)
问题引出:虽然ReentrantLock和ReentrantReaderWriter解决了并发访问下数据写入安全和效率的问题,但是如果出现非常多的线程的时候,有可能会造成一些线程一直阻塞。调度减少的情况。为此JUC提供了StampedLock(无障碍锁)该所的特点在于若干个读线程之间互不干预。并且可以保证多个写线程的独占操作
以下是StampedLock类的常用方法:
方法 | 描述 |
---|---|
readLock() | 获取读锁。 |
tryReadLock() | 尝试获取读锁,如果获取成功返回一个非负数,否则返回一个负数。 |
optimisticRead() | 获取一个乐观读锁。 |
tryConvertToOptimisticRead(stamp) | 尝试将锁从写锁转换为乐观读锁,如果转换成功返回一个非负数,否则返回一个负数。 |
tryConvertToReadLock() | 尝试将锁从乐观读锁转换为普通读锁,如果转换成功返回一个非负数,否则返回一个负数。 |
writeLock() | 获取写锁。 |
tryWriteLock() | 尝试获取写锁,如果获取成功返回一个非负数,否则返回一个负数。 |
unlock() | 释放锁。 |
unlockRead() | 释放读锁。 |
unlockWrite() | 释放写锁。 |
validate() | 验证锁是否仍然有效。 |
在StampedLock中有三种模式,乐观读,读,写用以提高并发处理性能,也用以转换锁的类型
案例代码:
假设我们有一个名为Point
的类,表示二维平面上的一个点,其中包含x坐标和y坐标。我们希望实现对这个点的读写操作,并且要确保在写操作时其他线程不能同时读取或写入。
package Example2113;import org.omg.CORBA.BAD_CONTEXT;import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.locks.StampedLock;class Point{
// 坐标double x;double y;private final StampedLock stampedLock = new StampedLock();public void set(double x,double y){
// 获取写锁long stamp = stampedLock.writeLock();try {this.x = x;this.y = y;}catch (Exception e){e.printStackTrace();}finally {
// 执行完后释放写锁stampedLock.unlockWrite(stamp);}}public double[] get(){
// 尝试获取乐观锁long stamp = stampedLock.tryOptimisticRead();double currentX =x;double currentY =y;
// 如果获取乐观锁失败则转为悲观锁if (!stampedLock.validate(stamp)){
// 获取stamp = stampedLock.readLock();try {currentX =x;currentY = y;}catch (Exception e){e.printStackTrace();}finally {stampedLock.unlockRead(stamp);}}return new double[]{currentX, currentY};}
}public class javaDemo {public static void main(String[] args) {Point point = new Point();Random random = new Random(10);for (int i =0;i<5;i++){new Thread(()->{point.set(random.nextDouble(), random.nextDouble());double recieve[] = point.get();System.out.println(Arrays.toString(recieve));}).start();}
// for (int j = 0;j<20;j++){
// new Thread(()->{
// double recieve[] = point.get();
// System.out.println(Arrays.toString(recieve));
// }).start();
// }}
}
问:乐观锁和悲观锁是什么?
乐观锁和悲观锁是并发编程中两种不同的锁策略。
乐观锁:乐观锁假设多个线程之间的并发冲突很少发生,所以它们可以同时读取共享数据而无需阻塞其他线程。在乐观锁中,线程首先尝试获取读锁,即乐观读取操作。如果没有发生写入冲突,那么乐观读锁会立即完成,并返回结果。但如果有其他线程在此期间进行了写入操作,则需要重新获取悲观锁再次尝试。
悲观锁:悲观锁假设多个线程之间的并发冲突经常发生,因此每个线程在访问共享数据之前会悲观地认为会发生冲突,所以必须先获得独占锁(写锁)或共享锁(读锁)。只有持有锁的线程完成操作后,其他线程才能访问共享数据。悲观锁会导致其他线程阻塞等待锁的释放,从而降低并发性能。
问:为什么要用Stamp,代码中的long stamp是什么意思
stamp
是一个标记,用于记录锁的状态。在读取锁和写入锁上下文之间传递,以确保数据一致性。
面试题 :请谈谈 ReadWriteLock 和 StampedLock
ReadWriteLock包括两种子锁
(1)ReadWriteLock
ReadWriteLock 可以实现多个读锁同时进行,但是读与写和写于写互斥,只能有一个写锁线程在进行。
(2)StampedLock
StampedLock是Jdk在1.8提供的一种读写锁,相比较ReentrantReadWriteLock性能更好,因为ReentrantReadWriteLock在读写之间是互斥的,使用的是一种悲观策略,在读线程特别多的情况下,会造成写线程处于饥饿状态,虽然可以在初始化的时候设置为true指定为公平,但是吞吐量又下去了,而StampedLock是提供了一种乐观策略,更好的实现读写分离,并且吞吐量不会下降。
StampedLock包括三种锁:
(1)写锁writeLock:
writeLock是一个独占锁写锁,当一个线程获得该锁后,其他请求读锁或者写锁的线程阻塞, 获取成功后,会返回一个stamp(凭据)变量来表示该锁的版本,在释放锁时调用unlockWrite方法传递stamp参数。提供了非阻塞式获取锁tryWriteLock。
(2)悲观读锁readLock:
readLock是一个共享读锁,在没有线程获取写锁情况下,多个线程可以获取该锁。如果有写锁获取,那么其他线程请求读锁会被阻塞。悲观读锁会认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据进行加锁,这是在读少写多的情况下考虑的。请求该锁成功后会返回一个stamp值,在释放锁时调用unlockRead方法传递stamp参数。提供了非阻塞式获取锁方法tryWriteLock。
(3)乐观读锁tryOptimisticRead:
tryOptimisticRead相对比悲观读锁,在操作数据前并没有通过CAS设置锁的状态,如果没有线程获取写锁,则返回一个非0的stamp变量,获取该stamp后在操作数据前还需要调用validate方法来判断期间是否有线程获取了写锁,如果是返回值为0则有线程获取写锁,如果不是0则可以使用stamp变量的锁来操作数据。由于tryOptimisticRead并没有修改锁状态,所以不需要释放锁。这是读多写少的情况下考虑的,不涉及CAS操作,所以效率较高,在保证数据一致性上需要复制一份要操作的变量到方法栈中,并且在操作数据时可能其他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性得到了保证。
面试题: 如何让 Java 的线程彼此同步?
- synchronized
- volatile
- ReenreantLock
使用局部变量实现线程同步
4.Condition(自定义锁)
在JUC中允许用户进行锁对象的创建,可以通过Condition接口实现。经常用于生产者消费者模型中。
以下是Condition接口的常用方法
方法 | 描述 |
---|---|
await() | 当前线程等待,并释放锁。等价于Object.wait() |
await(long time, TimeUnit unit) | 当前线程等待一段时间,并释放锁。等价于Object.wait() |
awaitUninterruptibly() | 当前线程不可中断地等待,并释放锁。 |
signal() | 唤醒一个等待该条件的线程。等价于Object.notify() |
signalAll() | 唤醒所有等待该条件的线程。等价于Object.notifyAll() |
使用案例:
假设有一个生产者-消费者模型,多个生产者线程负责向共享队列中生产数据,多个消费者线程负责从队列中消费数据。
package Example2114;import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class SharedQueue {private final Lock lock = new ReentrantLock();private final Condition notFull = lock.newCondition();private final Condition notEmpty = lock.newCondition();private static final int MAX_SIZE = 10;private Queue<Integer> queue = new LinkedList<>();public void produce(int num) throws InterruptedException {lock.lock();try {while (queue.size() == MAX_SIZE) {// 队列满时,等待条件notFullnotFull.await();}queue.offer(num);System.out.println("Produced: " + num);// 唤醒一个等待notEmpty条件的线程notEmpty.signal();} finally {lock.unlock();}}public int consume() throws InterruptedException {lock.lock();try {while (queue.isEmpty()) {// 队列空时,等待条件notEmptynotEmpty.await();}int num = queue.poll();System.out.println("Consumed: " + num);// 唤醒一个等待notFull条件的线程notFull.signal();return num;} finally {lock.unlock();}}
}public class javaDemo {public static void main(String[] args) {SharedQueue sharedQueue = new SharedQueue();// 创建生产者线程Thread producer1 = new Thread(() -> {try {for (int i = 0; i < 20; i++) {sharedQueue.produce(i);}} catch (InterruptedException e) {e.printStackTrace();}});Thread producer2 = new Thread(() -> {try {for (int i = 20; i < 40; i++) {sharedQueue.produce(i);}} catch (InterruptedException e) {e.printStackTrace();}});// 创建消费者线程Thread consumer1 = new Thread(() -> {try {for (int i = 0; i < 20; i++) {sharedQueue.consume();}} catch (InterruptedException e) {e.printStackTrace();}});Thread consumer2 = new Thread(() -> {try {for (int i = 0; i < 20; i++) {sharedQueue.consume();}} catch (InterruptedException e) {e.printStackTrace();}});// 启动线程producer1.start();producer2.start();consumer1.start();consumer2.start();}
}
案例2:实现缓存队列的读取:
package Example2116;import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;//缓冲读取与写入
class DataBuffer{private final Lock lock = new ReentrantLock();private final Condition notUll = lock.newCondition();private final Condition notEmpty = lock.newCondition();private static final int MAX_SIZE = 20;Queue<String> queue = new LinkedList<>();public void Set(String str){
// 获取互斥锁lock.lock();try {
// 当缓冲区满了while (queue.size()==MAX_SIZE){notUll.await();}
// 模拟网络延迟TimeUnit.SECONDS.sleep(2);
// 放入数据并尝试唤醒等待的消费者线程queue.offer(str);System.out.println(Thread.currentThread().getName()+"放入数据"+str);notEmpty.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public String Get() throws Exception{
// 使用互斥锁lock.lock();try {while (queue.isEmpty()){notEmpty.await();}String string = queue.poll();System.out.println("获取到缓冲区内容"+string);notUll.signal();return string;}finally {
// 解锁lock.unlock();}}
}public class javaDemo {public static void main(String[] args) {DataBuffer dataBuffer = new DataBuffer();for (int i=0;i<5;i++){new Thread(()->{for (int j = 0;j<20;j++){dataBuffer.Set("数据"+j+"号数、");}},"生产者"+i+"号").start();}for (int i=0;i<50;i++){new Thread(()->{for (int j=0;j<100;j++){try {System.out.println("消费者取走"+dataBuffer.Get());}catch (Exception e){}}}).start();}}
}
面试题: Java 如何实现多线程之间的通讯和协作?
Java中线程通信协作的最常见的两种方式:
1、syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
2、ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
5.LockSupport
由于JDK1.2时候为了预防死锁废除了一些Thread中的方法,但是一部分人认为这几个方法在操作上实际会更加直观,所以就有了LockSupport来替代这几个方法
LockSupport类的常用方法:
方法签名 | 说明 |
---|---|
park() | 阻塞当前线程,直到调用unpark(Thread thread) 或者中断当前线程。 |
park(Object blocker) | 阻塞当前线程,并关联一个特定的阻塞对象,用于调试和监控的目的。 |
parkNanos(long nanos) | 阻塞当前线程,最多阻塞指定的纳秒数,参数nanos 为等待时间。 |
parkNanos(Object blocker, long nanos) | 阻塞当前线程,并关联一个特定的阻塞对象,最多阻塞指定的纳秒数。 |
unpark(Thread thread) | 解除指定线程的阻塞状态。可以提前唤醒被阻塞的线程,使其继续执行。 |
案例代码:设置一个长辈线程和一个孩子辈线程,只有当长辈线程吃饭时候子线程才能进行吃饭
package Example2117;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;public class javaDemo {public static void main(String[] args) {
// 子线程Thread son = new Thread(()->{
// 阻塞线程LockSupport.park();System.out.println("子辈开始吃饭");});
// 父辈线程Thread father = new Thread(()->{try {TimeUnit.SECONDS.sleep(3);System.out.println("长辈开始吃饭");}catch (Exception e){e.printStackTrace();}finally {LockSupport.unpark(son);}});son.start();father.start();}
}
面试题:Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?
Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:
(1)可以使锁更公平
(2)可以使线程在等待锁的时候响应中断
(3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
(4)可以在不同的范围,以不同的顺序获取和释放锁
整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。
相关文章:

JUC并发编程之线程锁(一)
目录 1.ReentrantLock(互斥锁) 2.ReentRantReaderWriterLock(互斥读写锁) 3.StampedLock(无障碍锁) 4.Condition(自定义锁) 5.LockSupport 问题引出: 由于传统的线程控制需要用到同步机制Sy…...
Android AlertDialog标题居中
网上很多做法都是使用setCustomTitle方法实现的,我偏不,因为我已经找到了标题的textView了: 在show了之后可以拿到标题(注意一定是show之后才能拿得到,create之后拿也是空的): TextView title…...
k8s界面化平台dashboard、kubesphere、Rancher对比
k8s集群管理dashboard有很多,比如kuboard、官方发dashboard、kubesphere、Rancher等等。 Dashboard、KubeSphere 和 Rancher 都是流行的 Kubernetes 管理和操作界面。它们都提供了图形化的用户界面,以简化对 Kubernetes 集群的管理和监控。每个工具都有其…...
【字符串左旋】
字符串左旋 1.题目要求 实现一个函数,可以左旋字符串中的k个字符。 例如: ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 2.解法: 设计循环使其可以旋1次,然后让他执行n次是一个最简单的思路: 函数实现…...
Docker Dirtypipe(CVE-2022-0847)漏洞复现与分析容器逃逸
安装环境 ./metarget cnv install cve-2022-0847 --verbose 原理 同脏牛,通过写只读内存,对映射的内存做篡改 EXP docker run --rm -it -v $(pwd):/exp --cap-addCAP_DAC_READ_SEARCH ubuntu如果提示 Unknown capability to add: "CAP_CAP_DAC_RE…...

python接口自动化测试框架2.0,让你像Postman一样编写测试用例,支持多环境切换、多业务依赖、数据库断言等
项目介绍 接口自动化测试项目2.0 软件架构 本框架主要是基于 Python unittest ddt HTMLTestRunner log excel mysql 企业微信通知 Jenkins 实现的接口自动化框架。 前言 公司突然要求你做自动化,但是没有代码基础不知道怎么做?或者有自动化…...

Vue.js2+Cesium1.103.0 九、淹没分析效果
Vue.js2Cesium1.103.0 九、淹没分析效果 Demo <template><divid"cesium-container"style"width: 100%; height: 100%;"><spanid"button"style"position: absolute; right: 50px; top: 50px; z-index: 999; font-size: 24px…...

SpringBoot案例-部门管理-新增
根据页面原型,明确需求 页面原型 需求 阅读接口文档 接口文档链接如下: 【腾讯文档】SpringBoot案例所需文档 https://docs.qq.com/doc/DUkRiTWVaUmFVck9N 思路分析 前端在输入要新增的部门名称后,会以JSON格式将数据传入至后端…...
微信小程序中背景图片如何占满整个屏幕,拉伸
不变形 1. 在页面的wxss文件中,设置背景图片的样式: page{background-image: url(图片路径);background-size: 100% 100%;background-repeat: no-repeat; }2. 在页面的json文件中,设置背景图片的样式: {"backgroundTextStyl…...

Java并发编程(四)线程同步 中 [AQS/Lock]
概述 Java中可以通过加锁,来保证多个线程访问某一个公共资源时,资源的访问安全性。Java提出了两种方式来加锁 第一种是我们上文提到的通过关键字synchronized加锁,synchronized底层托管给JVM执行的,并且在java 1.6 以后做了很多…...

PyTorch深度学习环境安装(Anaconda、CUDA、cuDNN)及关联PyCharm
1. 关系讲解 Tytorch:Python机器学习库,基于Torch,用于自然语言处理等应用程序 Anaconda:是默认的python包和环境管理工具,安装了anaconda,就默认安装了conda CUDA:CUDA是一种由显卡厂商NVIDI…...

Active Directory安全和风险状况管理
风险评估和管理 风险评估和管理是主动安全性和合规性管理不可或缺的一部分。 发现关键基础设施组件中的风险行为和配置对于阻止网络入侵和预防网络攻击至关重要。帐户泄露和配置错误漏洞是用于破坏网络的常见技术。当评估、监控和降低 Active Directory 基础架构的风险时&…...
学术论文GPT源码解读:从chatpaper、chatwithpaper到gpt_academic
前言 之前7月中旬,我曾在微博上说准备做“20个LLM大型项目的源码解读” 针对这个事,目前的最新情况是 已经做了的:LLaMA、Alpaca、ChatGLM-6B、deepspeedchat、transformer、langchain、langchain-chatglm知识库准备做的:chatpa…...
单链表(C语言版)
单链表:理解、实现与应用 单链表(Singly Linked List)是一种常见的数据结构,用于存储一系列具有相同类型的元素,并通过节点之间的链接建立起它们的关系。每个节点包含一个数据元素和一个指向下一个节点的指针。相比于…...

初学vue3时应该注意的几个问题
初学vue3时应该注意的几个问题 声明响应式 响应式数据的声明在vue2的时候很简单,在data中声明就行了。但现在可以使用多个方式。 reactive用于声明Object, Array, Map, Set; ref用于声明String, Number, Boolean 使用reactive来声明基础数据类型(Str…...

基于Selenium技术方案的爬虫入门实践
通过爬虫技术抓取网页,动态加载的数据或包含 JavaScript 的页面,需要使用一些特殊的技术和工具。以下是一些常用的技术方法: 使用浏览器模拟器:使用像 Selenium、PhantomJS 或其他类似工具可以模拟一个完整的浏览器环境࿰…...

【C++入门到精通】C++入门 —— vector (STL)
阅读导航 前言一、vector简介1. 概念2. 特点 二、vector的使用1.vector 构造函数2. vector 空间增长问题⭕resize 和 reserve 函数 3. vector 增删查改⭕operator[] 函数 三、迭代器失效温馨提示 前言 前面我们讲了C语言的基础知识,也了解了一些数据结构࿰…...
git简单使用
1.在 远端仓库创建好仓库 2.在本地中创建仓库 mkdir 仓库名 cd 仓库名 3.初始化(可以省略) git init 4.添加远端仓库 git remote add origin https://gitee.com/zengtian_7/pet_home.git 5.初始化代码库:当你创建一个全新的代码库时,…...

CSS—选择器
目录 一、CSS简介 二、HTML页面中常用的元素 三、CSS语法规则 四、常用的选择器 五、CSS的三种使用方法 六、选择器参考 一、CSS简介 CSS (Cascading Style Sheets,层叠样式表),是一种用来为结构化文档(如 HTML 文档或 XML 应…...

【Unity实战系列】Unity的下载安装以及汉化教程
君兮_的个人主页 即使走的再远,也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们,这里是君兮_,怎么说呢,其实这才是我以后真正想写想做的东西,虽然才刚开始,但好歹,我总算是启程了。今天要分享…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...