当前位置: 首页 > article >正文

Java面试线程与锁,尸横遍野!

再谈多线程在我们的操作系统之上可以同时运行很多个进程并且每个进程之间相互隔离互不干扰。我们的CPU会通过时间片轮转算法为每一个进程分配时间片并在时间片使用结束后切换下一个进程继续执行通过这种方式来实现宏观上的多个程序同时运行。由于每个进程都有一个自己的内存空间进程之间的通信就变得非常麻烦比如要共享某些数据而且执行不同进程会产生上下文切换非常耗时那么有没有一种更好地方案呢后来线程横空出世一个进程可以有多个线程线程是程序执行中一个单一的顺序控制流程现在线程才是程序执行流的最小单元各个线程之间共享程序的内存空间也就是所在进程的内存空间上下文切换速度也高于进程。现在有这样一个问题public static void main(String[] args) { int[] arr new int[]{3, 1, 5, 2, 4}; //请将上面的数组按升序输出 }按照正常思维我们肯定是这样public static void main(String[] args) { int[] arr new int[]{3, 1, 5, 2, 4}; //直接排序吧 Arrays.sort(arr); for (int i : arr) { System.out.println(i); } }而我们学习了多线程之后可以换个思路来实现public static void main(String[] args) { int[] arr new int[]{3, 1, 5, 2, 4}; for (int i : arr) { new Thread(() - { try { Thread.sleep(i * 1000); //越小的数休眠时间越短优先被打印 System.out.println(i); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }我们接触过的很多框架都在使用多线程比如Tomcat服务器所有用户的请求都是通过不同的线程来进行处理的这样我们的网站才可以同时响应多个用户的请求要是没有多线程可想而知服务器的处理效率会有多低。在Java 5的时候新增了java.util.concurrentJUC包其中包括大量用于多线程编程的工具类目的是为了更好的支持高并发任务让开发者进行多线程编程时减少竞争条件和死锁的问题并发与并行我们经常听到并发编程那么这个并发代表的是什么意思呢而与之相似的并行又是什么意思它们之间有什么区别比如现在一共有三个工作需要我们去完成。顺序执行顺序执行其实很好理解就是我们依次去将这些任务完成了实际上就是我们同一时间只能处理一个任务所以需要前一个任务完成之后才能继续下一个任务依次完成所有任务。并发执行并发执行也是我们同一时间只能处理一个任务但是我们可以每个任务轮着做时间片轮转而我们Java中的线程正是这种机制当我们需要同时处理上百个上千个任务时很明显CPU的数量是不可能赶得上我们的线程数的所以说这时就要求我们的程序有良好的并发性能来应对同一时间大量的任务处理。学习Java并发编程能够让我们在以后的实际场景中知道该如何应对高并发的情况。并行执行并行执行就突破了同一时间只能处理一个任务的限制我们同一时间可以做多个任务比如我们要进行一些排序操作就可以用到并行计算只需要等待所有子任务完成最后将结果汇总即可。包括分布式计算模型MapReduce也是采用的并行计算思路。再谈锁机制谈到锁机制相信各位应该并不陌生了我们在JavaSE阶段通过使用synchronized关键字来实现锁这样就能够很好地解决线程之间争抢资源的情况。那么synchronized底层到底是如何实现的呢我们知道使用synchronized一定是和某个对象相关联的比如我们要对某一段代码加锁那么我们就需要提供一个对象来作为锁本身public static void main(String[] args) { synchronized (Main.class) { //这里使用的是Main类的Class对象作为锁 } }我们来看看它变成字节码之后会用到哪些指令其中最关键的就是monitorenter指令了可以看到之后也有monitorexit与之进行匹配注意这里有2个monitorenter和monitorexit分别对应加锁和释放锁在执行monitorenter之前需要尝试获取锁。每个对象都有一个monitor监视器与之对应而这里正是去获取对象监视器的所有权一旦monitor所有权被某个线程持有那么其他线程将无法获得管程模型的一种实现。在代码执行完成之后我们可以看到一共有两个monitorexit在等着我们那么为什么这里会有两个呢按理说monitorenter和monitorexit不应该一一对应吗这里为什么要释放锁两次呢首先我们来看第一个这里在释放锁之后会马上进入到一个goto指令跳转到15行而我们的15行对应的指令就是方法的返回指令其实正常情况下只会执行第一个monitorexit释放锁在释放锁之后就接着同步代码块后面的内容继续向下执行了。而第二个其实是用来处理异常的可以看到它的位置是在12行如果程序运行发生异常那么就会执行第二个monitorexit并且会继续向下通过athrow指令抛出异常而不是直接跳转到15行正常运行下去。实际上synchronized使用的锁就是存储在Java对象头中的我们知道对象是存放在堆内存中的而每个对象内部都有一部分空间用于存储对象头信息。而对象头信息中则包含了Mark Word用于存放hashCode和对象的锁信息在不同状态下它存储的数据结构有一些不同。重量级锁在JDK6之前synchronized一直被称为重量级锁monitor依赖于底层操作系统的Lock实现。Java的线程是映射到操作系统的原生线程上切换成本较高。而在JDK6之后锁的实现得到了改进。我们先从最原始的重量级锁开始我们说了每个对象都有一个monitor与之关联在Java虚拟机HotSpot中monitor是由ObjectMonitor实现的ObjectMonitor() { _header NULL; _count 0; //记录个数 _waiters 0, _recursions 0; _object NULL; _owner NULL; _WaitSet NULL; //处于wait状态的线程会被加入到_WaitSet _WaitSetLock 0 ; _Responsible NULL ; _succ NULL ; _cxq NULL ; FreeNext NULL ; _EntryList NULL ; //处于等待锁block状态的线程会被加入到该列表 _SpinFreq 0 ; _SpinClock 0 ; OwnerIsThread 0 ; }每个等待锁的线程都会被封装成ObjectWaiter对象进入到如下机制设计思路ObjectWaiter首先会进入 Entry Set等着当线程获取到对象的monitor后进入 The Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1若线程调用wait()方法将释放当前持有的monitorowner变量恢复为nullcount自减1同时该线程进入 WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor并复位变量的值以便其他线程进入获取对象的monitor。虽然这样的设计思路非常合理但是在大多数应用上每一个线程占用同步代码块的时间并不是很长我们完全没有必要将竞争中的线程挂起然后又唤醒并且现代CPU基本都是多核心运行的我们可以采用一种新的思路来实现锁。新思路在JDK1.4.2时引入了自旋锁JDK6之后默认开启它不会将处于等待状态的线程挂起而是通过无限循环的方式不断检测是否能够获取锁。由于单个线程占用锁的时间非常短所以说循环次数不会太多可能很快就能够拿到锁并运行这就是自旋锁。当然仅仅是在等待时间非常短的情况下自旋锁的表现会很好但是如果等待时间太长由于循环是需要处理器继续运算的所以这样只会浪费处理器资源因此自旋锁的等待时间是有限制的默认情况下为10次如果失败那么会进而采用重量级锁机制。在JDK6之后自旋锁得到了一次优化自旋的次数限制不再是固定的而是自适应变化的。比如在同一个锁对象上自旋等待刚刚成功获得过锁并且持有锁的线程正在运行那么这次自旋也是有可能成功的所以会允许自旋更多次。当然如果某个锁经常都自旋失败那么有可能会不再采用自旋策略而是直接使用重量级锁。轻量级锁从JDK 1.6开始为了减少获得锁和释放锁带来的性能消耗就引入了轻量级锁。轻量级锁的目标是在无竞争情况下减少重量级锁产生的性能消耗并不是为了代替重量级锁实际上就是赌一手同一时间只有一个线程在占用资源包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。它不像是重量级锁那样需要向操作系统申请互斥量。运作机制在即将开始执行同步代码块中的内容时会首先检查对象的Mark Word查看锁对象是否被其他线程占用如果没有任何线程占用那么会在当前线程中所处的栈帧中建立一个名为锁记录Lock Record的空间用于复制并存储对象目前的Mark Word信息官方称为Displaced Mark Word。接着虚拟机将使用CAS操作将对象的Mark Word更新为轻量级锁状态数据结构变为指向Lock Record的指针指向的是当前的栈帧CASCompare And Swap是一种无锁算法它并不会为对象加锁而是在执行的时候看看当前数据的值是不是我们预期的那样如果是那就正常进行替换如果不是那么就替换失败。比如有两个线程都需要修改变量i的值默认为10现在一个线程要将其修改为20另一个要修改为30如果他们都使用CAS算法那么并不会加锁访问i而是直接尝试修改i的值但是在修改时需要确认i是不是10如果是表示其他线程还没对其进行修改如果不是那么说明其他线程已经将其修改此时不能完成修改任务修改失败。在CPU中CAS操作使用的是cmpxchg指令能够从最底层硬件层面得到效率的提升。如果CAS操作失败了的话那么说明可能这时有线程已经进入这个同步代码块了这时虚拟机会再次检查对象的Mark Word是否指向当前线程的栈帧如果是说明不是其他线程而是当前线程已经有了这个对象的锁直接放心大胆进同步代码块即可。如果不是那确实是被其他线程占用了。这时轻量级锁一开始的想法就是错的这时有对象在竞争资源已经赌输了所以说只能将锁膨胀为重量级锁按照重量级锁的操作执行注意锁的膨胀是不可逆的如图所示所以轻量级锁 - 失败 - 自适应自旋锁 - 失败 - 重量级锁解锁过程同样采用CAS算法如果对象的MarkWord仍然指向线程的锁记录那么就用CAS操作把对象的MarkWord和复制到栈帧中的Displaced Mark Word进行交换。如果替换失败说明其他线程尝试过获取该锁在释放锁的同时需要唤醒被挂起的线程。偏向锁偏向锁相比轻量级锁更纯粹干脆就把整个同步都消除掉不需要再进行CAS操作了。它的出现主要是得益于人们发现某些情况下某个锁频繁地被同一个线程获取这种情况下我们可以对轻量级锁进一步优化。偏向锁实际上就是专门为单个线程而生的当某个线程第一次获得锁时如果接下来都没有其他线程获取此锁那么持有锁的线程将不再需要进行同步操作。可以从之前的MarkWord结构中看到偏向锁也会通过CAS操作记录线程的ID如果一直都是同一个线程获取此锁那么完全没有必要在进行额外的CAS操作。当然如果有其他线程来抢了那么偏向锁会根据当前状态决定是否要恢复到未锁定或是膨胀为轻量级锁。如果我们需要使用偏向锁可以添加-XX:UseBiased参数来开启。所以最终的锁等级为未锁定 偏向锁 轻量级锁 重量级锁值得注意的是如果对象通过调用hashCode()方法计算过对象的一致性哈希值那么它是不支持偏向锁的会直接进入到轻量级锁状态因为Hash是需要被保存的而偏向锁的Mark Word数据结构无法保存Hash值如果对象已经是偏向锁状态再去调用hashCode()方法那么会直接将锁升级为重量级锁并将哈希值存放在monitor有预留位置保存中。锁消除和锁粗化锁消除和锁粗化都是在运行时的一些优化方案。比如我们某段代码虽然加了锁但是在运行时根本不可能出现各个线程之间资源争夺的情况这种情况下完全不需要任何加锁机制所以锁会被消除。锁粗化则是我们代码中频繁地出现互斥同步操作比如在一个循环内部加锁这样明显是非常消耗性能的所以虚拟机一旦检测到这种操作会将整个同步范围进行扩展。JMM内存模型注意这里提到的内存模型和我们在JVM中介绍的内存模型不在同一个层次JVM中的内存模型是虚拟机规范对整个内存区域的规划而Java内存模型是在JVM内存模型之上的抽象模型具体实现依然是基于JVM内存模型实现的以前的文章有介绍。https://www.cnblogs.com/zwtblog/tag/侧边栏支持搜索。Java内存模型我们在计算机组成原理中学习过在我们的CPU中一般都会有高速缓存而它的出现是为了解决内存的速度跟不上处理器的处理速度的问题。所以CPU内部会添加一级或多级高速缓存来提高处理器的数据获取效率但是这样也会导致一个很明显的问题因为现在基本都是多核心处理器每个处理器都有一个自己的高速缓存那么又该怎么去保证每个处理器的高速缓存内容一致呢为了解决缓存一致性的问题需要各个处理器访问缓存时都遵循一些协议在读写时要根据协议来进行操作。这类协议有MSI、MESIIllinois Protocol、MOSI、Synapse、Firefly及Dragon Protocol等。而Java也采用了类似的模型来实现支持多线程的内存模型JMMJava Memory Model内存模型规定如下所有的变量全部存储在主内存注意这里包括下面提到的变量指的都是会出现竞争的变量包括成员变量、静态变量等而局部变量这种属于线程私有不包括在内每条线程有着自己的工作内存可以类比CPU的高速缓存线程对变量的所有操作必须在工作内存中进行不能直接操作主内存中的数据。不同线程之间的工作内存相互隔离如果需要在线程之间传递内容只能通过主内存完成无法直接访问对方的工作内存。也就是说每一条线程如果要操作主内存中的数据那么得先拷贝到自己的工作内存中并对工作内存中数据的副本进行操作操作完成之后也需要从工作副本中将结果拷贝回主内存中具体的操作就是Save保存和Load加载操作。那么各位肯定会好奇这个内存模型结合之前JVM所讲的内容具体是怎么实现的呢主内存对应堆中存放对象的实例的部分。工作内存对应线程的虚拟机栈的部分区域虚拟机可能会对这部分内存进行优化将其放在CPU的寄存器或是高速缓存中。比如在访问数组时由于数组是一段连续的内存空间所以可以将一部分连续空间放入到CPU高速缓存中那么之后如果我们顺序读取这个数组那么大概率会直接缓存命中。前面我们提到在CPU中可能会遇到缓存不一致的问题而Java中也会遇到比如下面这种情况public class Main { private static int i 0; public static void main(String[] args) throws InterruptedException { new Thread(() - { for (int j 0; j 100000; j) i; System.out.println(线程1结束); }).start(); new Thread(() - { for (int j 0; j 100000; j) i; System.out.println(线程2结束); }).start(); //等上面两个线程结束 Thread.sleep(1000); System.out.println(i); } }可以看到这里是两个线程同时对变量i各自进行100000次自增操作但是实际得到的结果并不是我们所期望的那样。那么为什么会这样呢在之前学习了JVM之后相信各位应该已经知道自增操作实际上并不是由一条指令完成的注意一定不要理解为一行代码就是一个指令完成的包括变量i的获取、修改、保存都是被拆分为一个一个的操作完成的那么这个时候就有可能出现在修改完保存之前另一条线程也保存了但是当前线程是毫不知情的。所以说在JavaSE阶段讲解这个问题的时候是通过synchronized关键字添加同步代码块解决的另外的解决方案原子类。重排序在编译或执行时为了优化程序的执行效率编译器或处理器常常会对指令进行重排序有以下情况编译器重排序Java编译器通过对Java代码语义的理解根据优化规则对代码指令进行重排序。机器指令级别的重排序现代处理器很高级能够自主判断和变更机器指令的执行顺序。指令重排序能够在不改变结果单线程的情况下优化程序的运行效率比如public static void main(String[] args) { int a 10; int b 20; System.out.println(a b); }我们其实可以交换第一行和第二行public static void main(String[] args) { int b 10; int a 20; System.out.println(a b); }即使发生交换但是我们程序最后的运行结果是不会变的当然这里只通过代码的形式演示实际上JVM在执行字节码指令时也会进行优化可能两个指令并不会按照原有的顺序进行。虽然单线程下指令重排确实可以起到一定程度的优化作用但是在多线程下似乎会导致一些问题public class Main { private static int a 0; private static int b 0; public static void main(String[] args) { new Thread(() - { if(b 1) { if(a 0) { System.out.println(A); }else { System.out.println(B); } } }).start(); new Thread(() - { a 1; b 1; }).start(); } }上面这段代码在正常情况下按照我们的正常思维是不可能输出A的因为只要b等于1那么a肯定也是1才对因为a是在b之前完成的赋值。但是如果进行了重排序那么就有可能a和b的赋值发生交换b先被赋值为1而恰巧这个时候线程1开始判定b是不是1了这时a还没来得及被赋值为1可能线程1就已经走到打印那里去了所以是有可能输出A的。volatile关键字关键字volatile开始之前我们先介绍三个词语原子性其实之前讲过很多次了就是要做什么事情要么做完要么就不做不存在做一半的情况。可见性指当多个线程访问同一个变量时一个线程修改了这个变量的值其他线程能够立即看得到修改的值。有序性即程序执行的顺序按照代码的先后顺序执行。我们之前说了如果多线程访问同一个变量那么这个变量会被线程拷贝到自己的工作内存中进行操作而不是直接对主内存中的变量本体进行操作。下面这个操作看起来是一个有限循环但是是无限的public class Main { private static int a 0; public static void main(String[] args) throws InterruptedException { new Thread(() - { while (a 0); System.out.println(线程结束); }).start(); Thread.sleep(1000); System.out.println(正在修改a的值...); a 1; //很明显按照我们的逻辑来说a的值被修改那么另一个线程将不再循环 } }实际上这就是我们之前说的虽然我们主线程中修改了a的值但是另一个线程并不知道a的值发生了改变所以循环中依然是使用旧值在进行判断因此普通变量是不具有可见性的。要解决这种问题我们第一个想到的肯定是加锁同一时间只能有一个线程使用这样总行了吧确实这样的话肯定是可以解决问题的public class Main { private static int a 0; public static void main(String[] args) throws InterruptedException { new Thread(() - { while (a 0) { synchronized (Main.class){} } System.out.println(线程结束); }).start(); Thread.sleep(1000); System.out.println(正在修改a的值...); synchronized (Main.class){ a 1; } } }但是除了硬加一把锁的方案我们也可以使用volatile关键字来解决。此关键字的第一个作用就是保证变量的可见性。当写一个volatile变量时JMM会把该线程本地内存中的变量强制刷新到主内存中去并且这个写会操作会导致其他线程中的volatile变量缓存无效。这样另一个线程修改了这个变时当前线程会立即得知并将工作内存中的变量更新为最新的版本。那么我们就来试试看public class Main { //添加volatile关键字 private static volatile int a 0; public static void main(String[] args) throws InterruptedException { new Thread(() - { while (a 0); System.out.println(线程结束); }).start(); Thread.sleep(1000); System.out.println(正在修改a的值...); a 1; } }结果还真的如我们所说的那样当a发生改变时循环立即结束。当然虽然说volatile能够保证可见性但是不能保证原子性要解决我们上面的i的问题可以使用加锁来完成public class Main { private static volatile int a 0; public static void main(String[] args) throws InterruptedException { Runnable r () - { for (int i 0; i 10000; i) a; System.out.println(任务完成); }; new Thread(r).start(); new Thread(r).start(); //等待线程执行完成 Thread.sleep(1000); System.out.println(a); } }volatile不是能在改变变量的时候其他线程可见吗那为什么还是不能保证原子性呢还是那句话自增操作是被瓜分为了多个步骤完成的虽然保证了可见性但是只要手速够快依然会出现两个线程同时写同一个值的问题比如线程1刚刚将a的值更新为100这时线程2可能也已经执行到更新a的值这条指令了已经刹不住车了所以依然会将a的值再更新为一次100那要是真的遇到这种情况那么我们不可能都去写个锁吧后面我们会介绍原子类来专门解决这种问题。最后一个功能就是volatile会禁止指令重排也就是说如果我们操作的是一个volatile变量它将不会出现重排序的情况.那么它是怎么解决的重排序问题呢若用volatile修饰共享变量在编译时会在指令序列中插入内存屏障来禁止特定类型的处理器重排序内存屏障Memory Barrier又称内存栅栏是一个CPU指令它的作用有两个保证特定操作的顺序保证某些变量的内存可见性volatile的内存可见性其实就是依靠这个实现的由于编译器和处理器都能执行指令重排的优化如果在指令间插入一条Memory Barrier则会告诉编译器和CPU不管什么指令都不能和这条Memory Barrier指令重排序。屏障类型指令示例说明LoadLoadLoad1;LoadLoad;Load2保证Load1的读取操作在Load2及后续读取操作之前执行StoreStoreStore1;StoreStore;Store2在Store2及其后的写操作执行前保证Store1的写操作已刷新到主内存LoadStoreLoad1;LoadStore;Store2在Store2及其后的写操作执行前保证Load1的读操作已读取结束StoreLoadStore1;StoreLoad;Load2保证load1的写操作已刷新到主内存之后load2及其后的读操作才能执行所以volatile能够保证之前的指令一定全部执行之后的指令一定都没有执行并且前面语句的结果对后面的语句可见。最后我们来总结一下volatile关键字的三个特性保证可见性不保证原子性防止指令重排在之后我们的设计模式系列视频中还会讲解单例模式下volatile的运用。happens-before原则经过我们前面的讲解相信各位已经了解了JMM内存模型以及重排序等机制带来的优点和缺点.综上JMM提出了happens-before先行发生原则定义一些禁止编译优化的场景来向各位程序员做一些保证只要我们是按照原则进行编程那么就能够保持并发编程的正确性。具体如下程序次序规则同一个线程中按照程序的顺序前面的操作happens-before后续的任何操作。同一个线程内代码的执行结果是有序的。其实就是可能会发生指令重排但是保证代码的执行结果一定是和按照顺序执行得到的一致程序前面对某一个变量的修改一定对后续操作可见的不可能会出现前面才把a修改为1接着读a居然是修改前的结果这也是程序运行最基本的要求。监视器锁规则对一个锁的解锁操作happens-before后续对这个锁的加锁操作。就是无论是在单线程环境还是多线程环境对于同一个锁来说一个线程对这个锁解锁之后另一个线程获取了这个锁都能看到前一个线程的操作结果。比如前一个线程将变量x的值修改为了12并解锁之后另一个线程拿到了这把锁对之前线程的操作是可见的可以得到x是前一个线程修改后的结果12所以synchronized是有happens-before规则的volatile变量规则对一个volatile变量的写操作happens-before后续对这个变量的读操作。就是如果一个线程先去写一个volatile变量紧接着另一个线程去读这个变量那么这个写操作的结果一定对读的这个变量的线程可见。线程启动规则主线程A启动线程B线程B中可以看到主线程启动B之前的操作。在主线程A执行过程中启动子线程B那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。线程加入规则如果线程A执行操作join()线程B并成功返回那么线程B中的任意操作happens-before线程Ajoin()操作成功返回。传递性规则如果A happens-before BB happens-before C那么A happens-before C。那么我们来从happens-before原则的角度来解释一下下面的程序结果public class Main { private static int a 0; private static int b 0; public static void main(String[] args) { a 10; b a 1; new Thread(() - { if(b 10) System.out.println(a); }).start(); } }首先我们定义以上出现的操作A将变量a的值修改为10B将变量b的值修改为a 1C主线程启动了一个新的线程并在新的线程中获取b进行判断如果为true那么就打印a首先我们来分析由于是同一个线程并且B是一个赋值操作且读取了A那么按照程序次序规则A happens-before B接着在B之后马上执行了C按照线程启动规则在新的线程启动之前当前线程之前的所有操作对新的线程是可见的所以 B happens-before C最后根据传递性规则由于A happens-before BB happens-before C所以A happens-before C因此在新的线程中会输出a修改后的结果10。

相关文章:

Java面试线程与锁,尸横遍野!

再谈多线程在我们的操作系统之上,可以同时运行很多个进程,并且每个进程之间相互隔离互不干扰。我们的CPU会通过时间片轮转算法,为每一个进程分配时间片,并在时间片使用结束后切换下一个进程继续执行,通过这种方式来实现…...

AS5600 OUT引脚功能详解

该提问与当前博客内容无直接关联。 AS5600是一款高精度、非接触式磁旋转位置传感器,其OUT引脚的功能与PWM输出模式是该芯片核心特性的具体体现,与其他传感器(如电位计、光编码器或模拟输出传感器)中的PWM信号在设计目的、信号特性…...

LeetCode 2602. 使数组元素全部相等的最少操作次数【排序,前缀和,二分】中等

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...

C++26反射在高频交易系统中的灰度实践(零停机元数据热重载技术首曝)

第一章:C26反射在高频交易系统中的灰度实践(零停机元数据热重载技术首曝) C26 标准草案中引入的 std::reflect 机制,首次为 C 带来编译期可查询、运行期可遍历的结构化类型元数据能力。在毫秒级延迟敏感的高频交易系统中&#xff…...

假冒视频会议软件“Meeten“正大规模窃取Web3从业者加密货币

攻击概述 网络犯罪分子正利用欺诈性视频会议平台感染Windows和Mac电脑,通过虚假的商务会议专门针对Web3行业从业者,以窃取加密货币资产。 这一恶意活动根据假冒会议软件的常用名称被称为 "Meeten",自2024年9月以来持续活跃。该恶…...

15门免费深度学习课程全解析:从入门到实战

1. 深度学习入门指南:15门免费在线课程全解析深度学习作为人工智能的核心技术,正在重塑各行各业的智能化进程。但很多初学者常常陷入"从何学起"的困境——市面上的付费课程动辄上千元,而免费资源又鱼龙混杂。我花了三个月时间系统测…...

基础算法——区间合并

题目给定n个区间[li,ri],要求合并所有有交集的区间。注意如果在端点处相交,也算有交集。输出合并完成后的区间个数。例如:[1,3]和[2,6]可以合并为一个区间[1,6]。输入格式第一行包含整数n。接下来n行,每行包含两个整数l和r。输出格…...

编码器-解码器模型中的注意力机制原理与应用

1. 编码器-解码器模型中的注意力机制解析在自然语言处理领域,编码器-解码器(Encoder-Decoder)架构是处理序列到序列(seq2seq)任务的经典框架。这个架构最初由两篇开创性论文提出:Ilya Sutskever等人的《Seq…...

WinUtil架构解析:模块化Windows系统管理框架的技术实现

WinUtil架构解析:模块化Windows系统管理框架的技术实现 【免费下载链接】winutil Chris Titus Techs Windows Utility - Install Programs, Tweaks, Fixes, and Updates 项目地址: https://gitcode.com/GitHub_Trending/wi/winutil 项目定位与技术背景 在Wi…...

ContextMenuManager:轻松掌控Windows右键菜单,打造个性化操作体验

ContextMenuManager:轻松掌控Windows右键菜单,打造个性化操作体验 【免费下载链接】ContextMenuManager 🖱️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 你是否曾经因为Windows…...

一招教你免费将DeepSeek、Qwen、Kimi、GLM、mimo等主流ai大模型网页转换成本地api任意调用 彻底实现token自由!

一招教你免费将DeepSeek、Qwen、Kimi、GLM、mimo等主流ai大模型网页转换成本地api任意调用 彻底实现token自由! 关键词:Chat2API教程、DeepSeek本地调用、Qwen本地API、OpenAI兼容接口、本地AI代理、多模型统一管理 名称链接Chat2API客户端 下载https://dooo.fun/a…...

2026获取微信小程序和小游戏AppID和Path教程(寻道大千刷邀请必备) 快速拿到用户id

2026获取微信小程序和小游戏AppID和Path教程(寻道大千刷邀请必备) 快速拿到用户id 关键词: 微信小程序AppID获取、微信小程序Path路径获取、小程序跳转参数、小程序自动化、小程序页面路径查询 标签: 微信小程序、AppID、Path路径、获取方法前言前段时间…...

Sunshine游戏串流终极指南:如何5分钟搭建跨设备游戏共享平台

Sunshine游戏串流终极指南:如何5分钟搭建跨设备游戏共享平台 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine Sunshine是一款开源的自托管游戏串流服务器,专…...

大语言模型输出优化与参数调优实战指南

1. 大语言模型输出优化指南 作为一名长期从事自然语言处理的技术从业者,我见证了大型语言模型(LLMs)从实验室走向产业应用的完整历程。这些模型确实改变了我们与技术交互的方式,但要让它们发挥最佳性能,需要掌握一些关键技巧。 LLMs本质上是…...

从AUTOSAR工程师视角看TDA4:那些官方SDK没告诉你的多核软件架构“坑”与实战调优

从AUTOSAR工程师视角看TDA4:多核软件架构的实战调优与避坑指南 当第一次拿到TDA4开发板时,我和团队都对这个号称"L2自动驾驶神器"的多核异构芯片充满期待。TI官方文档里那些华丽的性能参数——8TOPS算力、Cortex-A72R5FDSP的异构组合、高度集…...

知识点原子化拆解与专业讲解技能knowledge-explainer

Knowledge Explainer(SkillHub) Knowledge Explainer(ClawHub) name: knowledge-explainer author: 王教成 Wang Jiaocheng (波动几何) description: >- 知识点原子化拆解与专业讲解技能。将任意知识点拆解为不可再分的原子概念…...

【黑马点评日记】高并发秒杀:库存超卖与锁机制解析

🔥个人主页:北极的代码(欢迎来访) 🎬作者简介:java后端学习者 ❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb ✨命运的结局尽可永在,不屈的挑战却不可须臾或…...

永远不要让 Web 服务器以 root 运行。使用专用目录并限制权限。

它的本质是:通过身份隔离和文件系统沙箱,将 Web 应用可能遭受的攻击后果限制在“局部受损”,而非“系统崩溃”。如果 Web 服务器以 root 运行,任何代码漏洞(如文件上传、命令注入、反序列化)都将直接转化为…...

还在手写valgrind脚本?2026智能插件自动注入ASan/CFI/UBSan——3分钟完成企业级部署!

https://intelliparadigm.com 第一章:现代 C 语言内存安全编码规范 2026 插件下载与安装 插件获取渠道 现代 C 语言内存安全编码规范 2026(简称 MSC-2026)是一套面向 Clang/LLVM 生态的静态分析增强插件,集成 ASAN、CFI、SafeSt…...

UE5新手避坑:解决‘hostfxr.dll找不到’和.NET Core版本冲突的完整指南

UE5开发环境配置:彻底解决.NET Core依赖问题的实战手册 刚接触虚幻引擎5的开发者们,往往会在配置开发环境时遇到各种"拦路虎"。其中,.NET Core运行时问题堪称最典型的"新手杀手"——当你满怀期待地从版本控制系统拉取项…...

php.ini 中 session.save_path 指向的目录必须对 Web 用户可写,但其他用户不可读。

它的本质是:利用 Linux 的“粘滞位 (Sticky Bit)”和“目录执行权限”特性,构建一个 “公共投递箱” 模型。Web 服务器进程(如 www-data)可以往箱子里扔信件(创建 Session 文件),也可以取走自己…...

Vue项目里用vxe-grid做后台管理表格,我踩过的这些坑你千万别再踩了

Vue项目中vxe-grid实战避坑指南:工单管理模块深度解析 第一次在工单管理模块用vxe-grid实现动态可编辑表格时,我对着文档里那句"支持动态下拉选项"研究了整整三天。直到在Chrome调试器里看到column.editRender.props.options这个关键属性时&am…...

【毕设】厨艺交流平台设计与实现

💟博主:程序员俊星:CSDN作者、博客专家、全栈领域优质创作者 💟专注于计算机毕业设计,大数据、深度学习、Java、小程序、python、安卓等技术领域 📲文章末尾获取源码数据库 🌈还有大家在毕设选题…...

Oumuamua-7b-RP应用场景:日语JLPT N2备考者进行情景会话模拟训练

Oumuamua-7b-RP应用场景:日语JLPT N2备考者进行情景会话模拟训练 1. 项目概述 Oumuamua-7b-RP 是一款专为日语学习者设计的角色扮演对话工具,基于先进的Mistral-7B大语言模型架构开发。这个Web界面特别适合准备JLPT N2考试的学习者,通过模拟…...

Phi-3-mini-4k-instruct-gguf惊艳效果:生成符合PEP8规范的Python代码+单元测试

Phi-3-mini-4k-instruct-gguf惊艳效果:生成符合PEP8规范的Python代码单元测试 1. 模型简介与部署 Phi-3-Mini-4K-Instruct是一个38亿参数的轻量级开源模型,属于Phi-3系列中的Mini版本。该模型经过专门训练,能够生成高质量的代码内容&#x…...

**基于Geolocation API的精准位置服务开发实战:从原理到Vue3+JavaScript

基于Geolocation API的精准位置服务开发实战:从原理到Vue3JavaScript落地应用 在现代Web应用中,用户地理位置信息已成为提升体验的核心要素之一。无论是地图导航、本地化推荐,还是安全验证机制,Geolocation API 都是前端获取用户实…...

BOSS直接自动点击未读消息并发送求简历请求

最新版本代码 26-04-23 修复没有对话无法发送求简历按钮(() > {/******************************************************************* 可配置参数******************************************************************/const CONFIG {countdownSeconds: 3,openConfirmDe…...

13款降AI率工具实测:论文查重高怎么改,降重鸟稳居榜首

上周,我室友三天三夜敲完综述,维普AI率飙到46%,他抱头大喊“我又没抄”,我替他摸索工具,顺便吐槽:写得像人还被说像机器。 定向适配各大检测:平台选得对,降幅更稳 降重鸟地址&…...

别再死记公式了!用‘矩形面积’法秒懂均匀分布概率计算(附Python验证)

用几何直觉破解均匀分布:矩形面积法实战指南 想象一下,你经营着一家小花店,每天能卖出10到40束鲜花。某天有位老顾客要预订15到30束花,你想快速估算满足这个需求的概率——这时你需要的不是复杂的积分公式,而是一把直尺…...

Python 微信扫码活动系统实战

系统功能 1. 微信扫码入口 - 活动二维码:支持生成门店活动二维码,顾客扫码即可进入活动页面 - 活动识别:支持识别不同门店、不同活动场景 - 活动状态控制:支持活动启用、停用、过期控制 - 首次进入校验:校验用户是否首次参与、是否已领取、是否重复参与 2. 红包领取模…...