【JAVA】:万字长篇带你了解JAVA并发编程【一】

目录

  • 【JAVA】:万字长篇带你了解JAVA并发编程
    • 1. 并发编程基础
      • 并发与并行
        • 并发(Concurrency)
        • 并行(Parallelism)
      • 线程与进程
      • 线程的状态与生命周期
      • 线程同步与锁
    • 2. Java并发工具类
      • 准备:多线程测试工具类
      • synchronized关键字
      • ReentrantLock
        • 基本语法
        • 可中断 `lockInterruptibly()`
        • 设置超时时间 tryLock()
        • 公平锁 new ReentrantLock(true)
        • 条件变量 Condition
      • ReadWriteLock
      • CountDownLatch
      • CyclicBarrier
        • await方法
      • Semaphore
      • Exchanger
      • Phaser
    • 结语

个人主页: 【⭐️个人主页】
需要您的【💖 点赞+关注】支持 💯


【JAVA】:万字长篇带你了解JAVA并发编程

1. 并发编程基础

并发与并行

并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。

不过这种解释非常晦涩难懂,对于基础和涉世未深的开发人员可能来说如同天书一样。

那我们就回溯本源,来讲一下他们的区别。

并发(Concurrency)

早期计算机的 CPU 都是单核的,一个 CPU 在同一时间只能执行一个进程/线程(任务),当系统中有多个进程/线程任务等待执行时,CPU 只能执行完一个再执行下一个。
计算机在运行过程中,有很多指令会涉及 I/O 操作,而 I/O 操作又是相当耗时的,速度远远低于 CPU,这导致 CPU 经常处于空闲状态,只能等待 I/O 操作完成后才能继续执行后面的指令。
为了提高 CPU 利用率,减少等待时间,人们提出了一种 CPU 并发工作的理论。

所以谈起并发,我们可以想象成,一条质检员(CPU)想要质检多条流水线(线程/进程)的物品(任务)。这个工人来回在多个生产线上质检货品的操作就是并发.

将 CPU 资源合理地分配给多个任务共同使用,有效避免了 CPU 被某个任务长期霸占的问题,极大地提升了 CPU 资源利用率。

并行(Parallelism)

并发是针对单核 CPU 提出的,而并行则是针对多核 CPU 提出的。和单核 CPU 不同,多核 CPU 真正实现了“同时执行多个任务”。

多核 CPU 的每个核心都可以独立地执行一个任务,而且多个核心之间不会相互干扰。在不同核心上执行的多个任务,是真正地同时运行,这种状态就叫做并行

理解:
并行的话,就更好理解了。直接增加质检员。每个质检员之间的工作独立。每个质检员都有自己处理的流水线。极大的提高了整个系统的性能。相当于直接是物理层面的改变。

并发针对单核 CPU 而言,它指的是 CPU 交替执行不同任务的能力;并行针对多核 CPU 而言,它指的是多个核心同时执行多个任务的能力。
单核 CPU 只能并发,无法并行;换句话说,并行只可能发生在多核 CPU 中。
在多核 CPU 中,并发和并行一般都会同时存在,它们都是提高 CPU 处理任务能力的重要手段。

线程与进程

【JAVA】多线程:一文快速了解多线程

线程的状态与生命周期

【JAVA】多线程:一文快速了解多线程

线程同步与锁

Java中的同步和锁是多线程编程中重要的概念,用于保证线程安全,避免竞态条件。
在多线程编程中,如果多个线程同时访问共享资源,就可能出现竞态条件,导致数据不一致或其他问题。因此,需要采取措施来保证线程安全,这就是同步和锁的作用。

同步:是指在多线程中,为了保证线程安全,使得一组线程按照一定的顺序执行,不会出现竞态条件。在Java中,可以使用synchronized关键字实现同步。

:是指对共享资源的访问控制,同一时刻只能有一个线程持有锁,并且其他线程无法访问该资源。在Java中,可以使用synchronized关键字、Lock接口及其实现类等方式实现锁。
可以阅读另一篇文章

❤️‍🔥 一文让你看懂并发编程中的锁

2. Java并发工具类

准备:多线程测试工具类

public class SimpleThreadUtils {/*** 创建循环测试线程** @param threadCount 线程数量* @param threadName  线程名* @param loopCount   循环次数* @param consumer    执行代码 参数*/public static  List<Thread> newLoopThread(int threadCount, String threadName, long loopCount, Consumer<Thread> consumer) {List<Thread> list = new ArrayList<>();for (int i = 0; i < threadCount; i++) {final int threadNo = i;Thread thread =  new Thread(() -> {System.out.println("当前线程-"+ Thread.currentThread().getName()+": 开始");int j = 0;while (j++ < loopCount) {try {Thread.sleep(100);} catch (InterruptedException e) {}consumer.accept(Thread.currentThread());}System.out.println("当前线程-"+ Thread.currentThread().getName()+": 结束");}, threadName + ":" + i);thread.start();list.add(thread);}return list;}public static void wait(long gap , Runnable runnable) {while (true) {try {Thread.sleep(gap);runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

synchronized关键字

和其他的线程安全技术一样,synchronized关键字的作用也是为了保障数据的原子性、可见性和有序性,只是相比于其他技术,synchronized资历更老,历史更久,而且也更基础,基本上我们在学习线程相关内容的时候,就会学习这个关键字。
特点:

  • 互斥性(共享锁)
  • 可重入

在用法上,synchronized关键字可以修饰变量、方法和代码块,修饰不同的对象最终产生的影响范围也有所不同,下面我们通过一些简单示例,来看下synchronized修饰不同的对象所产生的效果:
加在方法上,默认的共享锁变量是当前对象实例this

    public  synchronized void updateSafe(int value) {int sum = this.value + value;try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}this.value = sum;}

加载代码块中

    public void updateSafeBlock(int value) {synchronized (this) {int sum = this.value + value;try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}this.value = sum;}}

ReentrantLock

相对于synchronized 它具备如下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • 与 synchronized 一样,都支持可重入

ReentrantLock是可重入的互斥锁,虽然具有与synchronized相同功能,但是会比synchronized更加灵活

基本语法
    // 获取ReentrantLock对象ReentrantLock lock = new ReentrantLock();public void saveSafe(int money) {// 加锁 获取不到锁一直等待直到获取锁lock.lock();try {// 临界区save(money);// 需要执行的代码} finally {// 释放锁 如果不释放其他线程就获取不到锁lock.unlock();}}
可中断 lockInterruptibly()

synchronizedreentrantlock.lock() 的锁, 是不可被打断的; 也就是说别的线程已经获得了锁, 线程就需要一直等待下去,不能中断,直到获得到锁才运行。

一个线程等待锁的时候,是可以被中断。通过中断异常报出。处理中断使其继续执行逻辑

 @Testpublic void testLockInterupted() {ReentrantLock lock = new ReentrantLock();// 主线程获取锁lock.lock();AtomicReference<Thread> t1 = new AtomicReference<>();SimpleThreadUtils.newLoopThread(2, "Kx", 1000, (t) -> {if (t.getName().contains("0")) {if (t1.get() == null){t1.set(t);}try {System.out.println("获取锁 线程:" + t.getName() + ":::");// 获取锁: 阻塞 科中断lock.lockInterruptibly();// 不可中断lock.lock();System.out.println("获取锁成功  线程:" + t.getName() + ":::");}catch (InterruptedException e){System.out.println(" 被打断  线程:" + t.getName() + ":::");}} else {if (t1.get() != null) {t1.get().interrupt();System.out.println("中断 线程:" + t.getName() + ":::");}else {System.out.println("中断 线程 没有准备:" + t.getName() + ":::");}}});SimpleThreadUtils.wait(10000, () -> {System.out.println("监控:" + reentrantLockDemo.getMoney());});}
设置超时时间 tryLock()

使用 lock.tryLock() 方法会返回获取锁是否成功。如果成功则返回true,反之则返回false。

并且tryLock方法可以设置指定等待时间,参数为:tryLock(long timeout, TimeUnit unit) , 其中timeout为最长等待时间,TimeUnit为时间单位

获取锁的过程中, 如果超过等待时间, 或者被打断, 就直接从阻塞队列移除, 此时获取锁就失败了, 不会一直阻塞着 ! (可以用来实现死锁问题)

tryLock适合我们再预期判断是否可以获得锁的前提下进行业务处理。而不需要一直等待,占用线程资源。

当我们tryLock的时候,表示我们试着获取锁,如果已经被其他线程占用,那么就可以直接跳过处理,提示用户资源被处理中。

  @Testpublic void testLockTryLock() {ReentrantLock lock = new ReentrantLock();// SimpleThreadUtils.SimpleThreadUtils.newLoopThread(10, "threadname", 1000, (t) -> {boolean b = lock.tryLock();try {try {if(b) {System.out.println(t.getName() + "获取到锁,处理一分钟");Thread.sleep(1000);}else {System.out.println(t.getName() + "获取到锁失败");Thread.sleep(1000);}} catch (InterruptedException e) {}} finally {if (b) {lock.unlock();}}});SimpleThreadUtils.wait(10000, () -> {System.out.println("监控:" + reentrantLockDemo.getMoney());});}}

结果:

当前线程-t:1: 开始
当前线程-t:0: 开始
当前线程-t:2: 开始
t:1获取到锁失败
t:2获取到锁,处理一分钟
t:0获取到锁失败
t:0获取到锁,处理一分钟
t:2获取到锁失败
t:1获取到锁失败
t:2获取到锁失败
t:0获取到锁失败
t:1获取到锁,处理一分钟
公平锁 new ReentrantLock(true)

ReentrantLock默认是非公平锁, 可以指定为公平锁new ReentrantLock(true)
在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的。
一般不设置ReentrantLock为公平的, 会降低并发度.
Synchronized底层的Monitor锁就是不公平的, 和谁先进入阻塞队列是没有关系的。

条件变量 Condition

传统对象等待集合只有一个 waitSet, Lock可以通过newCondition()方法 生成多个等待集合Condition对象。 Lock和Condition 是一对多的关系
配合方法 await()signal()

使用流程

  1. await 前需要 获得
  2. await 执行后,会释放锁,进入 conditionObject (条件变量)中等待
  3. await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  4. 竞争 lock 锁成功后,从 await 后继续执行
  5. signal 方法用来唤醒条件变量(等待室)汇总的某一个等待的线程
  6. signalAll方法, 唤醒条件变量(休息室)中的所有线程
  @Testpublic void testCondition() {ReentrantLock lock = new ReentrantLock();// 等待 A(条件变量)Condition ac = lock.newCondition();// 等外 B(条件变量)Condition bc = lock.newCondition();SimpleThreadUtils.newLoopThread(2, "t", 1000, (t) -> {try {print("Lock..................");lock.lock();if (t.getNo() == 0){print("等待...");ac.await();print("等待结束..");}else if(t.getNo() == 1){print("通知取消等待...");ac.signal();print("通知取消等待执行...");}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}});SimpleThreadUtils.wait(10000, () -> {});}

ReadWriteLock

ReadWriteLock也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个资源可以被多个线程同时读,或者被一个线程写,但是不能同时存在读和写线程。

读锁:共享锁 readLock

写锁: 独占锁 writeLock

读写锁一定要注意几点。

  1. 一定要记得读锁和写锁之间的竞争关系。只有读锁与读锁之间是不存在竞争,其他都会产生锁竞争,锁阻塞。
  2. 对于读锁而言,由于同一时刻可以允许多个线程访问共享资源,进行读操作,因此称它为共享锁;而对于写锁而言,同一时刻只允许一个线程访问共享资源,进行写操作,因此称它为排他锁。
  3. 记住锁的通用范式,一定要释放锁
public class ReadWriteLockDemo {private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();private int value;public int getValue() {System.out.println("读:" + value);return value;}public void update(int value) {int sum = this.value + value;try {Thread.sleep(10);} catch (InterruptedException e) {}this.value = sum;}public int read() {Lock lock = readWriteLock.readLock();try {lock.lock();Thread.sleep(10);System.out.println("读锁:after");return getValue();} catch (InterruptedException e) {throw new IllegalArgumentException();} finally {lock.unlock();}}public void updateSafe(int value) {Lock lock = readWriteLock.writeLock();try {System.out.println("writeLock:before");lock.lock();Thread.sleep(1000);System.out.println("writeLock:after");update(value);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}

CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。这就是一个计数器一样。
CountDownLatch定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,这里的计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器。

例子: 游戏加载资源

只有5个资源线程同时加载完成,游戏才能开始

public class CountDownLatchTest {@Testpublic void test() throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(5);SimpleThreadUtils.newLoopThread(5, "cd", 1, (t) -> {// 资源加载逻辑int i = t.getNo() * 1000;try {// 模拟加载时间Thread.sleep(i);} catch (InterruptedException e) {}System.out.println(t.getName()+": 花费了 " + i + "s");// 计数器-1countDownLatch.countDown();});// 阻塞等待 计数器为0. 所有资源加载完成countDownLatch.await();System.out.println("开始游戏");}
}

CyclicBarrier

CyclicBarrier可以理解为一个循环栅栏,其实和CountDownLatch有一定的相同点。就是都是需要进行计数,CyclicBarrier是等待所有人都准备就绪了,才会进行下一步。不同点在于,CyclicBarrier结束了一次计数之后会自动开始下一次计数。而CountDownLatch只能完成一次。

CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
CyclicBarrier内部使用了ReentrantLock和Condition两个类。它有两个构造函数

public CyclicBarrier(int parties) {this(parties, null);
}public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;this.count = parties;this.barrierCommand = barrierAction;
}

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程使用await()方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier的另一个构造函数CyclicBarrier(int parties, Runnable barrierAction),用于线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。

await方法

调用await方法的线程告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞。直到parties个参与线程调用了await方法,CyclicBarrier同样提供带超时时间的await和不带超时时间的await方法

     @Testpublic void test() throws InterruptedException {CyclicBarrier cyclicBarrier = new CyclicBarrier(5);SimpleThreadUtils.newLoopThread(5, "cd", 1, (t) -> {while (true) {int i = t.getNo() * 1000;try {Thread.sleep(i);} catch (InterruptedException e) {}System.out.println(":" + t.getName() + ": 准备好了");try {cyclicBarrier.await();System.out.println(" 冲。。。 :" + t.getName());} catch (InterruptedException e) {throw new RuntimeException(e);} catch (BrokenBarrierException e) {throw new RuntimeException(e);}}});SimpleThreadUtils.wait(10000,()->{});}

Semaphore

Semaphore主要是用来控制资源数量的工具,可以理解为信号量。
初始化时给定信号量的个数,其他线程可以来尝试获得许可,当完成任务之后需要释放响应的许可。
这样同时并行/并发处理的数量最大为我们指定的数量。约束住同时访问资源的数量限制。

@Testpublic void test() throws InterruptedException {Semaphore semaphore = new Semaphore(5);SimpleThreadUtils.newLoopThread(10, "cd", 10, (t) -> {try {semaphore.acquire();int i = t.getNo() * 1000;try {Thread.sleep(i);} catch (InterruptedException e) {}System.out.println("加载资源线程:"+t.getName()+": 花费了 " + i + "s");} catch (InterruptedException e) {throw new RuntimeException(e);}finally {semaphore.release();}});SimpleThreadUtils.wait(10000,()->{});}

Exchanger

Exchanger是一个用于线程间协作的工具类。它提供了一个交换的同步点,在这个交换点两个线程能够交换数据。具体交换数据的方式是通过exchange函数实现的,如果一个线程先执行exchange函数,那么它会等待另一个线程也执行exchange方法。当两个线程都到达了同步交换点,两个线程就可以交换数据。

两个人同时到达指定的地点,然后留下信息,exchanger把信息交换传输。

  @Testpublic void test() {Exchanger<String> exchanger = new Exchanger<>();SimpleThreadUtils.newLoopThread(2,"ex",2,(t)->{if (t.getNo() == 0){try {String exchange = exchanger.exchange("我先到了,你死定了");System.out.println(t.getName()+":"+exchange);} catch (InterruptedException e) {}}else {try {Thread.sleep(5000);String exchange = exchanger.exchange("先到了吧,去死吧");System.out.println(t.getName()+":"+exchange);} catch (InterruptedException e) {throw new RuntimeException(e);}}});SimpleThreadUtils.wait(1000,()->{});}
}

Phaser

PhaserJava7新引入的并发API,我们可以将Phaser的概念看成是一个个的阶段, 每个阶段都需要执行的线程任务,任务执行完毕后就进入下一阶段。这里和CyclicBarrier 和CountDownLatch的概念类似, 实际上也确实可以用Phaser代替CyclicBarrier和CountDownLatch。

Phaser也是通过计数器来控制, 在Phaser中叫parties, 我们在指定了parties之后, Phaser可以根据需要动态增加或减少parties的值

register()//添加一个新的注册者
bulkRegister(int parties)//添加指定数量的多个注册者
arrive()// 到达栅栏点直接执行,无须等待其他的线程
arriveAndAwaitAdvance()//到达栅栏点,必须等待其他所有注册者到达
arriveAndDeregister()//到达栅栏点,注销自己无须等待其他的注册者到达
onAdvance(int phase, int registeredParties)//多个线程达到注册点之后,会调用该方法。

结语

并发针对单核 CPU 而言,它指的是 CPU 交替执行不同任务的能力;并行针对多核 CPU 而言,它指的是多个核心同时执行多个任务的能力。
单核 CPU 只能并发,无法并行;换句话说,并行只可能发生在多核 CPU 中。
在多核 CPU 中,并发和并行一般都会同时存在,它们都是提高 CPU 处理任务能力的重要手段。

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

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

相关文章

FPGA驱动步进电机-Sin曲线加速

FPGA驱动步进电机-Sin曲线加速 基本实现原理实际仿真的波形程序 以下由特权同学的FPGA文档摘取 Sin 曲线控制 step 脉冲信号生成的功能框图如下所示。 基本实现原理 ①判断步进电机驱动的目标频率 stepper_delay_target 与当前频率 stepper_delay_current的值是否一致&#…

360智慧生活旗舰产品率先接入“360智脑”能力实现升级

10月25日&#xff0c;360智慧生活秋季新品及视觉云方案发布会在深圳召开。360智能硬件产品&#xff0c;诸如 360可视门铃、360智能摄像机、360行车记录仪、360儿童手表和家庭防火墙等&#xff0c;都在各自的行业有着举足轻重得地位&#xff0c;而这次发布的系列新品&#xff0c…

2023高频前端面试题-vue

1. 什么是 M V VM Model-View-ViewModel 模式 Model 层: 数据模型层 通过 Ajax、fetch 等 API 完成客户端和服务端业务模型的同步。 View 层: 视图层 作为视图模板存在&#xff0c;其实 View 就是⼀个动态模板。 ViewModel 层: 视图模型层 负责暴露数据给 View 层&…

【软考】__集成_考前20问

我总是临时抱佛脚&#xff01;分数刚刚好~~~ 目录 01. 对于不同规模和类别的项目&#xff0c;初步可行性研究可能出现4种结果&#xff0c;即&#xff1a; 02. 缩短活动工期可采取哪些措施&#xff1f; 03. 三点估算法&#xff08;悲观乐观4最可能&#xff09;/6 04. 挣值分…

mysql 命令行安装

一、安装包下载 1、下载压缩包 &#xff08;1&#xff09;公众号获取 关注微信公众号【I am Walker】&#xff0c;回复“mysql”获取 &#xff08;2&#xff09;官网下载 安装地址MySQL :: Download MySQL Community Server ​ ​ 二、解压 下载完之后进行解压&…

vscode代码快捷输入

Vscode代码片段快捷输入 常用的代码片段为了避免重复输入,可以使用Vsco的中用户代码片段进行设置,这样就可以实现快捷输入. 操作流程 如下 打开vscode的设置 2. 找到用户代码片段 3. 选择模板 4. 然后写入代码片段即可 上面的代码片段可以设置多个,看自己 重点关注的是 prefi…

勒索病毒最新变种.locked1勒索病毒来袭,如何恢复受感染的数据?

引言&#xff1a; 在当今数字化时代&#xff0c;网络威胁不断进化&#xff0c;.locked1勒索病毒就是其中一种常见的恶意软件。这种病毒会加密您的文件&#xff0c;然后勒索赎金以解锁它们。本文将详细介绍.locked1勒索病毒&#xff0c;包括如何恢复被加密的数据文件和如何预防…

Elasticsearch基础篇(六):es映射和常用的字段类型

es创建映射和设置 一、什么是 Elasticsearch 映射&#xff1f;二、映射中的字段类型常见字段类型 &#xff08;Common data types&#xff09;对象和关联类型&#xff08;Objects and relational types&#xff09;结构化数据类型&#xff08;Structured data types&#xff09…

微信小程序投票管理系统:打造智能、便捷的投票体验

前言 随着社交网络的兴起和移动互联网的普及&#xff0c;人们对于参与和表达意见的需求越来越强烈。在这个背景下&#xff0c;微信小程序投票管理系统应运而生。它为用户提供了一个智能、便捷的投票平台&#xff0c;使用户可以轻松创建和参与各种类型的投票活动。本文将详细介…

嚼一嚼Halcon中的3D手眼标定

文章目录 一、问题概述1、何为手眼标定&#xff1f;2、手眼标定的2种形式1&#xff09;眼在手上&#xff08;eye in hand&#xff09;&#xff1a;即相机固定在机械臂末端2&#xff09;眼在手外&#xff08;eye to hand&#xff09;&#xff1a;即相机固定在机械臂以外的地方 3…

微信小程序:点击按钮出现右侧弹窗

效果 代码 wxml <!-- 弹窗信息 --> <view class"popup-container" wx:if"{{showPopup}}"><view class"popup-content"><!-- 弹窗内容 --><text>这是一个右侧弹窗</text></view> </view> <…

Linux:实用操作

Linux&#xff1a;实用操作 1. 各类小技巧1.1 controlc(ctrl c) 强制停止1.2 可以通过快捷键&#xff1a;control d(ctrl d)&#xff0c;退出账户的登录1.3 历史命令搜索1.4 光标移动快捷键 2. 软件安装2.1 介绍2.2 yum命令(需要root权限)在这里插入图片描述 3. systemctl4.…

Java CC 解析 SQL 语法示例

示例&#xff1a;SimpleSelectParser 解析 select 11; 输出 2&#xff1b; 0&#xff09;总结 编写 JavaCC 模板&#xff0c;*.jj 文件。 编译生成代码文件。 移动代码文件到对应的包下。 调用生成的代码文件。 1&#xff09;JavaCC 模板 main/javacc/SimpleSelectParse…

【疯狂Java】数组

1、一维数组 (1)初始化 ①静态初始化&#xff1a;只指定元素&#xff0c;不指定长度 new 类型[] {元素1,元素2,...} int[] intArr; intArr new int[] {5,6,7,8}; ②动态初始化&#xff1a;只指定长度&#xff0c;不指定元素 new 类型[数组长度] int[] princes new in…

Transformer模型 | iTransformer时序预测

Transformer 模型在自然语言处理和计算机视觉领域取得了巨大的成功,并成为了基础模型。然而,最近一些研究开始质疑基于Transformer的时间序列预测模型的有效性。这些模型通常将同一时间戳的多个变量嵌入到不可区分的通道中,并在这些时间标记上应用注意力机制来捕捉时间依赖关…

二、PHP基础学习[变量]

部分内容引用自&#xff1a;https://blog.csdn.net/lady_killer9/article/details/108978062 一、PHP基础学习 1.语法与注释 示例&#xff1a; <?php // PHP 代码/* 这是 PHP 多行 注释 */ ?>2.输出 示例&#xff1a;echo 123; 3.变量 规矩&#xff1a; 变量以 …

【Overload游戏引擎细节分析】Lambert材质Shader分析

一、经典光照模型&#xff1a;Phong模型 现实世界的光照是极其复杂的&#xff0c;而且会受到诸多因素的影响&#xff0c;这是以目前我们所拥有的处理能力无法模拟的。经典光照模型冯氏光照模型(Phong Lighting Model)通过单独计算光源成分得到综合光照效果&#xff0c;然后添加…

城市正视图(Urban Elevations, ACM/ICPC World Finals 1992, UVa221)rust解法

如图5-4所示&#xff0c;有n&#xff08;n≤100&#xff09;个建筑物。左侧是俯视图&#xff08;左上角为建筑物编号&#xff0c;右下角为高度&#xff09;&#xff0c;右侧是从南向北看的正视图。 输入每个建筑物左下角坐标&#xff08;即x、y坐标的最小值&#xff09;、宽度…

sysfs接口与用户空间库libgpiod

sysfs接口是Linux内核提供的一种让用户空间程序访问和控制内核对象&#xff08;包括设备、驱动等&#xff09;信息的机制&#xff0c;它基于虚拟文件系统&#xff0c;将内核对象信息以文件系统的形式表现出来&#xff0c;用户或程序员可以通过读写这些文件来获取或修改内核对象…

React之diff原理

一、是什么 跟Vue一致&#xff0c;React通过引入Virtual DOM的概念&#xff0c;极大地避免无效的Dom操作&#xff0c;使我们的页面的构建效率提到了极大的提升 而diff算法就是更高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处 传统diff算法通过循环递归对节点进行依…