Java面试知识点(全)- Java并发- Java并发基础一

news2025/4/5 18:07:42

Java面试知识点(全)
导航: https://nanxiang.blog.csdn.net/article/details/130640392
注:随时更新

多线程解决什么问题

CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:
CPU 增加了缓存,以均衡与内存的速度差异;// 导致可见性问题
操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致原子性问题
编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致有序性问题#

Java怎么解决并发问题

  • 理解的第一个维度:核心知识点
    JMM本质上可以理解为,Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。具体来说,这些方法包括
    volatile、synchronized 和 final 三个关键字
    Happens-Before 规则

  • 理解的第二个维度:可见性,有序性,原子性

  • 原子性
    在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行

    Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

  • 可见性
    Java提供了volatile关键字来保证可见性。

    当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

    而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

    另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

  • 有序性

    在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。当然JMM是通过Happens-Before 规则来保证有序性的。

怎么解决线程安全

  1. 互斥同步
    synchronized 和 ReentrantLock。
  2. 非阻塞同步
    互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
    互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。
  • CAS
    随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略: 先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。
    乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是: 比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。

  • AtomicInteger
    J.U.C 包里面的整数原子类 AtomicInteger,其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作。

  1. 无同步方案
    要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。
  • 栈封闭
    多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
  • 线程本地存储(Thread Local Storage)
    如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。

线程状态和转变

在这里插入图片描述

  • 新建(New)
    创建后尚未启动。
  • 可运行(Runnable)
    可能正在运行,也可能正在等待 CPU 时间片。
    包含了操作系统线程状态中的 Running 和 Ready。
  • 阻塞(Blocking)
    等待获取一个排它锁,如果其线程释放了锁就会结束此状态。
  • 无限期等待(Waiting)
    等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

进入方法 退出方法
没有设置 Timeout 参数的 Object.wait() 方法 Object.notify() / Object.notifyAll()
没有设置 Timeout 参数的 Thread.join() 方法 被调用的线程执行完毕
LockSupport.park() 方法 -

  • 限期等待(Timed Waiting)
    无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
    调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述,不会释放锁。
    调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述,会释放锁。
    睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
    阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。

进入方法 退出方法
Thread.sleep() 方法 时间结束
设置了 Timeout 参数的 Object.wait() 方法 时间结束 / Object.notify() / Object.notifyAll()
设置了 Timeout 参数的 Thread.join() 方法 时间结束 / 被调用的线程执行完毕
LockSupport.parkNanos() 方法 -
LockSupport.parkUntil() 方法 -

  • 死亡(Terminated)
    可以是线程结束任务之后自己结束,或者产生了异常而结束。

守护线程 Daemon

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
main() 属于非守护线程。使用 setDaemon() 方法将一个线程设置为守护线程。

线程的中断方式

一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。

  • InterruptedException
    通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
    对于以下代码,在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。

  • interrupted()
    如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。
    但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

  • Executor 的中断操作
    调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。

线程间协作

  • join()
    在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
    对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
public class JoinExample {

    private class A extends Thread {
        @Override
        public void run() {
            System.out.println("A");
        }
    }

    private class B extends Thread {

        private A a;

        B(A a) {
            this.a = a;
        }

        @Override
        public void run() {
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }

    public void test() {
        A a = new A();
        B b = new B(a);
        b.start();
        a.start();
    }

    public static void main(String[] args) {
	    JoinExample example = new JoinExample();
	    example.test();
	}
}
//结果
A
B

  • wait() notify() notifyAll()
    调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
    它们都属于 Object 的一部分,而不属于 Thread。
    只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateExeception。
    使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。
    wait() 和 sleep() 的区别
    wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
    wait() 会释放锁,sleep() 不会。
    样例:

public class WaitNotifyCase {
    public static void main(String[] args) {
        final Object lock = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        lock.wait();
                        System.out.println("execute thread 1");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        TimeUnit.SECONDS.sleep(5);
                        System.out.println("execute thread 2");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                }
            }
        }).start();
    }
}

  • await() signal() signalAll()
    java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

Synchronized作用域

对象锁
方法锁
类锁

Synchronized本质原理

Synchronized由JVM实现的。

  • 加锁和释放锁的原理
    深入JVM看字节码,创建如下的代码:
public class SynchronizedDemo {
    Object object = new Object();
	    public void method1() {
	        synchronized (object) {
			}
	    }
}

使用javac命令进行编译生成.class文件

vac SynchronizedDemo.java
使用javap命令反编译查看.class文件的信息
javap -verbose SynchronizedDemo.class
得到如下的信息:

在这里插入图片描述

关注红色方框里的monitorenter和monitorexit即可。
Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。每一个对象在同一时间只与一个monitor(锁)相关联,而一个monitor在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的Monitor锁的所有权的时候,monitorenter指令会发生如下3中情况之一:

  • monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待
  • 如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加
  • 这把锁已经被别的线程获取了,等待锁释放
    monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。
    下图表现了对象,对象监视器,同步队列以及执行线程状态之间的关系:

在这里插入图片描述

该图可以看出,任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。

  • 可重入原理:加锁次数计数器
    看如下的例子:
public class SynchronizedDemo {
	public static void main(String[] args) {
	        synchronized (SynchronizedDemo.class) {
	}
	        method2();
	    }
	private synchronized static void method2() {
	}
}

对应的字节码

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                
         2: dup
         3: astore_1
         4: monitorenter
         5: aload_1
         6: monitorexit
         7: goto          15
        10: astore_2
        11: aload_1
        12: monitorexit
        13: aload_2
        15: invokestatic  #3                  // Method method2:()V
      Exception table:
         from    to  target type
             5     7    10   any
            10    13    10   any

上面的SynchronizedDemo中在执行完同步代码块之后紧接着再会去执行一个静态同步方法,而这个方法锁的对象依然就这个类对象,那么这个正在执行的线程还需要获取该锁吗? 答案是不必的,从上图中就可以看出来,执行静态同步方法的时候就只有一条monitorexit指令,并没有monitorenter获取锁的指令。这就是锁的重入性,即在同一锁程中,线程不需要再次获取同一把锁。
Synchronized先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。

  • 保证可见性的原理:内存模型和happens-before规则
    Synchronized的happens-before规则,即监视器锁规则:对同一个监视器的解锁,happens-before于对该监视器的加锁。继续来看代码:
public class MonitorDemo {
    private int a = 0;
	public synchronized void writer() {     // 1
	        a++;                                // 2
	    }                                       // 3
	public synchronized void reader() {    // 4
	        int i = a;                         // 5
	    }                                      // 6
}

该代码的happens-before关系如图所示:
在这里插入图片描述

在图中每一个箭头连接的两个节点就代表之间的happens-before关系,黑色的是通过程序顺序规则推导出来,红色的为监视器锁规则推导而出:线程A释放锁happens-before线程B加锁,蓝色的则是通过程序顺序规则和监视器锁规则推测出来happens-befor关系,通过传递性规则进一步推导的happens-before关系。现在我们来重点关注2 happens-before 5,通过这个关系我们可以得出什么?
根据happens-before的定义中的一条:如果A happens-before B,则A的执行结果对B可见,并且A的执行顺序先于B。线程A先对共享变量A进行加一,由2 happens-before 5关系可知线程A的执行结果对线程B可见即线程B所读取到的a的值为1。

Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法

简单来说在JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的,但是由于使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行,这种切换的代价是非常昂贵的;然而在现实中的大部分情况下,同步方法是运行在单线程环境(无锁竞争环境)如果每次都调用Mutex Lock那么将严重的影响程序的性能。不过在jdk1.6中对锁的实现引入了大量的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销。

  • 锁粗化(Lock Coarsening):也就是减少不必要的紧连在一起的unlock,lock操作,将多个连续的锁扩展成一个范围更大的锁。
  • 锁消除(Lock Elimination):通过运行时JIT编译器的逃逸分析来消除一些没有在当前同步块以外被其他线程共享的数据的锁保护,通过逃逸分析也可以在线程本地Stack上进行对象空间的分配(同时还可以减少Heap上的垃圾收集开销)。
  • 轻量级锁(Lightweight Locking):这种锁实现的背后基于这样一种假设,即在真实的情况下我们程序中的大部分同步代码一般都处于无锁竞争状态(即单线程执行环境),在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁,取而代之的是在monitorenter和monitorexit中只需要依靠一条CAS原子指令就可以完成锁的获取及释放。当存在锁竞争的情况下,执行CAS指令失败的线程将调用操作系统互斥锁进入到阻塞状态,当锁被释放的时候被唤醒。
  • 偏向锁(Biased Locking):是为了在无锁竞争的情况下避免在锁获取过程中执行不必要的CAS原子指令,因为CAS原子指令虽然相对于重量级锁来说开销比较小但还是存在非常可观的本地延迟。
  • 适应性自旋(Adaptive Spinning):当线程在获取轻量级锁的过程中执行CAS操作失败时,在进入与monitor相关联的操作系统重量级锁(mutex semaphore)前会进入忙等待(Spinning)然后再次尝试,当尝试一定的次数后如果仍然没有成功则调用与该monitor关联的semaphore(即互斥锁)进入到阻塞状态。

Synchronized锁升级

偏向锁:只有一个线程争抢锁资源的时候.将线程拥有者标识为当前线程.
轻量级锁(自旋锁):一个或多个线程通过CAS去争抢锁,如果抢不到则一直自旋.
重量级锁:多个线程争抢锁,向内核申请锁资源,将未争抢成功的锁放到队列中直接阻塞.

为什么要有锁的升级过程?
在最开始的时候,其实就是无锁直接到重量级锁,但是重量级锁需要向内核申请额外的锁资源,这就涉及到用户态和内核态的转换,比较浪费资源,而且大多数情况下,其实还是一个线程去争抢锁,完全不需要重量级锁.

锁升级
当只有一个线程去争抢锁的时候,会先使用偏向锁,就是给一个标识,说明现在这个锁被线程a占有.
后来又来了线程b,线程c,说凭什么你占有锁,需要公平的竞争,于是将标识去掉,也就是撤销偏向锁,升级为轻量级锁,三个线程通过CAS进行锁的争抢(其实这个抢锁过程还是偏向于原来的持有偏向锁的线程).
现在线程a占有了锁,线程b,线程c一直在循环尝试获取锁,后来又来了十个线程,一直在自旋,那这样等着也是干耗费CPU资源,所以就将锁升级为重量级锁,向内核申请资源,直接将等待的线程进行阻塞.

Synchronized由什么样的缺陷? Java Lock是怎么弥补这些缺陷的?

  • synchronized的缺陷
    1. 效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时
    2. 不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活
    3. 无法知道是否成功获得锁,相对而言,Lock可以拿到状态
  • Lock解决相应问题
    Lock类这里不做过多解释,主要看里面的4个方法:
    1. lock(): 加锁
    2. unlock(): 解锁
    3. tryLock(): 尝试获取锁,返回一个boolean值
    4. tryLock(long,TimeUtil): 尝试获取锁,可以设置超时

Synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合解决了这个问题。
多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。

Synchronized和Lock的对比,和选择

  • 存在层次上
    synchronized: Java的关键字,在jvm层面上
    Lock: 是一个接口
  • 锁的释放
    synchronized: 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁
    Lock: 在finally中必须释放锁,不然容易造成线程死锁
  • 锁的获取
    synchronized: 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待
    Lock: 分情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待(可以通过tryLock判断有没有锁)
  • 锁的释放(死锁产生)
    synchronized: 在发生异常时候会自动释放占有的锁,因此不会出现死锁
    Lock: 发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生
  • 锁的状态
    synchronized: 无法判断
    Lock: 可以判断
  • 锁的类型
    synchronized: 可重入 不可中断 非公平
    Lock: 可重入 可判断 可公平(两者皆可)
  • 性能
    synchronized: 少量同步
    Lock: 大量同步
    Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离) 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
    ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
  • 调度
    synchronized: 使用Object对象本身的wait 、notify、notifyAll调度机制
    Lock: 可以使用Condition进行线程之间的调度
  • 用法
    synchronized: 在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
    Lock: 一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
  • 底层实现
    synchronized: 底层使用指令码方式来控制锁的,映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当线程执行遇到monitorenter指令时会尝试获取内置锁,如果获取锁则锁计数器+1,如果没有获取锁则阻塞;当遇到monitorexit指令时锁计数器-1,如果计数器为0则释放锁。
    Lock: 底层是CAS乐观锁,依赖AbstractQueuedSynchronizer类,把所有的请求线程构成一个CLH队列。而对该队列的操作均通过Lock-Free(CAS)操作。

synchronized是公平锁吗?

synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象。

参考文档:https://www.pdai.tech/md/interview/x-interview.html

外传

😜 原创不易,如若本文能够帮助到您的同学
🎉 支持我:关注我+点赞👍+收藏⭐️
📝 留言:探讨问题,看到立马回复
💬 格言:己所不欲勿施于人 扬帆起航、游历人生、永不言弃!🔥

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

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

相关文章

PMP课堂模拟题目及解析(第11期)

101. 一家咨询公司的负责人启动一个项目来扩大公司提供的服务数量,这公司具有竞争优势、出色的企业知识以及卓越的声誉,高管团队担心与增加新服务相关的负面业务结果的可能性。若要评估负面业务结果的可能性和影响,项目经理应该使用什么&…

matlab写入txt文件进行自动化测试总结:fopen、fclose和fprintf的用法

前言 日常学习的过程中使用了matlab读写txt文件,记录一下基本函数的使用,本文主要介绍了fopen、fclose和fprintf几个函数,这些主要是面向txt格式的文件保存数据。还有其他几个函数,比如fread和fwrite,用过但是他们是针…

【dcdc】AP2813 DCDC降压恒流芯片 两路输出 一路恒流 一路瀑闪 电动摩托汽车灯方案

1,方案来源:深圳市世微半导体有限公司 汤巧 2,产品描述 AP2813 是一款双路降压恒流驱动器,高效率、外围简单、内置功率管,适用于 5-80V 输入的高精度降压 LED 恒流驱动芯片。内置功率管输出最大功率可达12W,最大电流…

图神经网络+强化学习

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 车辆路径规划问题(VRP)是运筹优化领域最经典的优化问题之一。在此问题中,有若干个客户对某种货物有一定量的需求,车辆可以从仓库取货之后配送到客户手中。客户点与仓库点组成了一…

DSP_TMS320F28377D_一键烧写多核程序

以前在开发和调试TMS320F28377D的双核程序的时候,总是在烧写CPU1程序时,自动把CPU2的程序也烧写了,但往CPU2里面烧写的是CPU1的程序,烧写完进入在线仿真模式的时候,还需要手动重新选择CPU2要烧写的程序,重新…

谈谈Netty线程模型

大家好,我是易安! Netty是一个高性能网络应用框架,应用非常普遍,目前在Java领域里,Netty基本上成为网络程序的标配了。Netty框架功能丰富,也非常复杂,今天我们主要分析Netty框架中的线程模型&am…

【数据分享】2014-2023年全国监测站点的逐年空气质量数据(15个指标\shp\excel格式)

空气质量的好坏反映了空气的污染程度,在各项涉及城市环境的研究中,空气质量都是一个十分重要的指标。空气质量是依据空气中污染物浓度的高低来判断的。 我们发现学者王晓磊在自己的主页里面分享了2014年5月以来的全国范围的到站点的逐时空气质量数据&am…

网络安全--红队资源大合集

红队攻击的生命周期,整个生命周期包括: 信息收集、攻击尝试获得权限、持久性控制、权限提升、网络信息收集、横向移动、数据分析(在这个基础上再做持久化控制)、在所有攻击结束之后清理并退出战场。 重点提醒:本项目…

JVM内存区域(一)

运行时数据区域 ** 线程私有的: 程序计数器虚拟机栈本地方法栈线程共享的: 线程共享的: 堆方法区直接内存 (非运行时数据区的一部分) 程序计数器 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示…

15-02 身份安全

身份安全——认证 目录管理系统 身份认证 你知道什么:密码、PIN、密码短语你拥有什么:硬令牌、智能卡、USB卡、手机APP指纹、声纹、脸纹、虹膜 授权和访问控制 访问控制 访问控制原则 最小特权:安全管理员禁止访问任何资源默认拒绝&…

【005】C++数据类型之实型(浮点数)、有符号数以及无符号数

C数据类型之实型、有符号数以及无符号数 引言一、实型(浮点数)1.1、实型常量1.2、实型变量 二、有符号数三、无符号数总结 引言 💡 作者简介:专注于C/C高性能程序设计和开发,理论与代码实践结合,让世界没有…

Eolink 出席 QECon 深圳站,共同探讨软件质量和效能发展

5月12日至13日,由 QECon 组委会和深圳市软件行业协会联合主办的「QECon全球软件质量&效能大会」成功召开,作为国内 API 全生命周期解决方案的领军者,Eolink 受邀参加此次大会。 大会中,Eolink SaaS 产品负责人崔嘉杰、高级售…

《思考致富》不应该指望不经历“暂时的失败”便能发财

目录 作者简介 经典摘录 机遇有个狡猾的习惯,喜欢从后门悄悄溜进来,往往还喜欢以灾难或暂时失败的方式乔装露面 离金矿仅有三英尺远 欲望:成就一切的起点(通往致富之路的第一步) 信念:在脑海里目睹并坚…

网络安全萌新先学什么?后学什么?

在选择网络安全行业之前,我们要弄清楚,要问一下自己的内心,自己为什么要进入这个行业?每个人的答案肯定是不一样的。 肯定有人会说:这个行业比很多其他行业更赚钱 有人会说:对网络安全技术非常感兴趣 有人会…

Web3和低代码开发:下一代Web应用开发的合作与创新

Web3作为区块链技术的一部分,被认为是下一代互联网技术的主要方向。与此同时,低代码开发作为快捷而高效的软件创建工具,也一直得到广泛关注。那么,Web3和低代码开发如何合作,激发出下一代Web应用开发的新生力量呢&…

前端性能优化:如何提高页面加载速度和用户体验

第一章:介绍 当今互联网时代,网站的性能对于用户体验至关重要。一个快速加载的网页不仅能提高用户的满意度,还能增加页面的转化率。而在前端开发中,性能优化是一个永恒的话题。本篇博客将为大家分享一些关于前端性能优化的技巧和…

红色元宇宙数字展厅:三维构建身临其境的红色历史之旅

导语:红色,是中国革命的象征,是我们历史中最为壮丽的篇章之一。然而,随着时间的推移,许多珍贵的红色记忆逐渐模糊,年轻一代对于红色历史的认知也渐行渐远。 红色元宇宙数字展厅,作为一种全新的互…

2023年软件测试前景?自动化测试的未来?我的测试之路高歌猛进...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 Python自动化测试&…

网络计算模式复习(五)

结构化P2P&#xff1a;直接根据查询内容的关键字定位其索引的存放节点 DHT算法 将内容索引抽象为<K,V>对 K是内容关键字的Hash摘要&#xff1a;KHash(key) V是存放内容的实际位置&#xff0c;例如节点IP地址等所有的<K,V>对组成一张大的Hash表&#xff0c;该表存…

异地远程连接威联通NAS,无需公网IP

文章目录 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 转载自远程内网穿透的文章&#xff1a;无需公网IP&#xff0c;在外远程访问NAS威联通QNAP【…