并发编程总结(二)

 

目录

 

Java 对象头

wait / notify

sleep(long n) 和 wait(long n) 的区别

死锁

定位死锁

饥饿

ReentrantLock


Java 对象头

        以 32 位虚拟机为例

        64 位虚拟机 Mark Word

        在程序中查看对象结构:

        导入依赖:

        <!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

        编写代码:

package com.example.demo.test;

import org.openjdk.jol.info.ClassLayout;

public class MyMain {
    static User user=new User();
    static User[] users=new User[10];
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
        System.out.println(ClassLayout.parseInstance(users).toPrintable());
    }
}
class User{
    private String username;
    private String password;
}

        输出结果:

        对象头由Mark Word、klass pointer两部分组成,如果对象是数组,则还要加上数组长度,即三部分组成。

        Mark Word由64位8个字节组成。

        klass pointer由64位8个字节组成,但我们使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。即上面截图中的klass pointer为4个字节32位。

        类指针klass pointer和数组长度,很简单这里不在描述,重点描述下Mark Word部分。

        Mark Word的64位,不同的位表示的意思不一样,具体如下所示:

|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |       
|--------------------------------------------------------------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |    GC
|--------------------------------------------------------------------------------------------------------------|

        lock:  锁状态标记位,该标记的值不同,整个mark word表示的含义不同。

        biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

        age:Java GC标记位对象年龄,4位的表示范围为0-15,因此对象经过了15次垃圾回收后如果还存在,则肯定会移动到老年代中。

        identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。

        thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。

        epoch:偏向时间戳。

        ptr_to_lock_record:指向栈中锁记录的指针。

        ptr_to_heavyweight_monitor:指向线程Monitor的指针。

wait / notify

        obj.wait() 让进入 object 监视器的线程到 waitSet 等待
        obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
        obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
        它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法。
final static Object obj = new Object();
public static void main(String[] args) {
 new Thread(() -> {
 synchronized (obj) {
 log.debug("执行....");
 try {
 obj.wait(); // 让线程在obj上一直等待下去
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 log.debug("其它代码....");
 }
 }).start();
 new Thread(() -> {
 synchronized (obj) {
 log.debug("执行....");
 try {
 obj.wait(); // 让线程在obj上一直等待下去
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 log.debug("其它代码....");
 }
 }).start();
 // 主线程两秒后执行
 sleep(2);
 log.debug("唤醒 obj 上其它线程");
 synchronized (obj) {
 obj.notify(); // 唤醒obj上一个线程
 // obj.notifyAll(); // 唤醒obj上所有等待线程
 }
}
        notify 的一种结果
20:00:53.096 [Thread-0] c.TestWaitNotify - 执行.... 
20:00:53.099 [Thread-1] c.TestWaitNotify - 执行.... 
20:00:55.096 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
20:00:55.096 [Thread-0] c.TestWaitNotify - 其它代码....
        notifyAll 的结果
19:58:15.457 [Thread-0] c.TestWaitNotify - 执行.... 
19:58:15.460 [Thread-1] c.TestWaitNotify - 执行.... 
19:58:17.456 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
19:58:17.456 [Thread-1] c.TestWaitNotify - 其它代码.... 
19:58:17.456 [Thread-0] c.TestWaitNotify - 其它代码....
        wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到 notify 为止。
        wait(long n) 有时限的等待 , n 毫秒后结束等待,或是被 notify。

sleep(long n) wait(long n) 的区别

        1) sleep 是 Thread 方法,而 wait Object 的方法
        2) sleep 不需要强制和 synchronized 配合使用,但 wait 需要 和 synchronized 一起用
        3) sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
        4) 它们状态 TIMED_WAITING

死锁

        有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。
         定义:在两个或多个并发进程中,如果每个进程持有某种资源而又都等待着别的进程释放它或它们现在保持着的资源,否则就不能向前推进,此时每个进程都占用了一定的资源但又都不能向前推进,称这一组进程产生了死锁。
         产生原因:1、系统资源不足;2、进程推进顺序非法。

        产生死锁的四个必要条件:

  • 互斥条件:涉及的资源是非共享的。
  • 不剥夺条件:进程所获得的资源在未使用完毕之前不能被其它进程强行夺走。
  • 部分分配:进程每次申请它所需要的一部分资源,在等待新资源的同时继续占用已分配到的资源。
  • 环路条件:存在着一种进程的循环链,链中的每一个进程已获得的资源同时被链中的下一个进程请求。
死锁的解决方法:

        预防:通过设置某些限制条件,以破坏产生死锁的四个条件中的一个或者几个,来防止发生死锁。

        根据生产死锁的四个必要条件,只要使用其中之一不能成立,死锁就不会出现。但必要条件①是由设备的固有特性所决定的,不仅不能改变,相反还应加以保证,因此实际上只有三种方法。

        预防死锁是一种较易实现的方法,已被广泛使用,但由于所施加的相知条件往往太严格,可能导致系统资源利用率和系统吞吐量降低。即具体可通过以下3种方法实现:

        1、防止部分分配(摒弃请求和保持条件)

        系统要求任一进程必须预先申请它所需要的全部资源,而且仅当该进程的全部资源要求能得到满足时,系统才能给予一次性分配,然后启动该进程运行,但是在分配时只要有一种资源不满足,系统就不会给进程分配资源。进程运行期间,不会再请求新的资源,所以,再分配就不会发生(摒弃了部分分配)。其特点为:资源严重浪费,进程延迟进行。

        2、防止“不剥夺”条件的出现

        采用的策略:一个已经保持了某些资源的进程,当它再提出新的资源要求而不能立即得到满足时,必须释放它已经保持的所有资源,待以后需要时再重新申请。此方法实现比较复杂,且要付出很大代价;此外,还因为反复地申请和释放资源,而使进程的执行无限地推迟,延长了周转时间,增加了系统的开销,降低了系统吞吐量。(例如打印机打印的结果不连续)

        3、防止“环路等待”条件的出现

        采用资源顺序使用法,基本思想是:把系统中所有资源类型线性排队,并按递增规则赋予每类资源以唯一的编号,例如输入机=1,打印机=2,磁带机=3,硬盘=4等等。进程申请资源时,必须严格按资源编号的递增顺序进行,否则系统不予分配。

        优点:资源利用率和系统吞吐量与另两种方法相比有较明显的改善。

        缺点:1、为系统中各种类型的资源所分配的序号必须相对稳定,这就限制了新设备类型的增加。2、作业实际使用资源的顺序与系统规定的顺序不同而造成资源的浪费。

        避免:系统在分配资源时根据资源的使用情况提前作出预测,从而避免死锁的发生。

        避免:避免死锁与预防死锁的区别在于:预防死锁是设法至少破坏产生死锁的必要条件之一,严格地防止死锁的出现。

        避免死锁,它是在进程请求分配资源时,采用某种算法(银行家算法)来预防可能发生的死锁,从而拒绝可能引起死锁的某个资源请求。

        在避免死锁(或银行家算法)中必须谈到两种系统状态:①安全状态,指系统能按照某种顺序,为每个进程分配所需的资源,直至最大需求,使得每个进程都能顺利完成。②非安全状态:即在某个时刻系统中不存在一个安全序列,则称系统处于不安全状态或非安全状态。

        虽然并非所有不安全状态都是死锁状态,但当系统进入不安全状态后,便有可能进入死锁状态;反之只要系统处于安全状态,系统便可避免进入死锁状态。因此,避免死锁的实质是如何使系统不进入不安全状态。

        检测:允许系统在运行的过程中产生死锁,但是,系统中有相应的管理模块可以及时检测出已经产生的死锁,并且精确地确定与死锁有关的进程和资源,然后采取适当措施,清除系统中已经产生的死锁。

        死锁的检测:检查死锁的办法就是由软件检查系统中由进程和资源构成的有向图是否构成一个或多个环路,若是,则存在死锁,否则不存在。其实质是确定是否存在环路等待现象,一但发现这种环路便认定死锁存在,并识别出该环路涉及的有关进程,以供系统采取适当的措施来解除死锁。

        解除:与检测死锁相配套的一种措施,用于将进程从死锁状态下解脱出来。

        死锁的解除:1、将陷入死锁的进程全部撤销。2、逐个作废死锁进程,直至死锁不再存在。3、从死锁进程中逐个地强迫抢占其所占资源,直至死锁不存在。
        t1 线程 获得 A 对象 锁,接下来想获取 B 对象 的锁 t2 线程 获得 B 对象 锁,接下来想获取 A 对象 的锁例:
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
     synchronized (A) {
         log.debug("lock A");
         sleep(1);
         synchronized (B) {
             log.debug("lock B");
             log.debug("操作...");
         }
     }
}, "t1");
Thread t2 = new Thread(() -> {
     synchronized (B) {
         log.debug("lock B");
         sleep(0.5);
         synchronized (A) {
             log.debug("lock A");
             log.debug("操作...");
         }
     }
}, "t2");
t1.start();
t2.start();

定位死锁

        检测死锁可以使用 jconsole 工具,或者使用 jps 定位进程 id ,再用 jstack 定位死锁:
cmd > jps
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
12320 Jps
22816 KotlinCompileDaemon
33200 TestDeadLock // JVM 进程
11508 Main
28468 Launcher
cmd > jstack 33200
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
2018-12-29 05:51:40
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.91-b14 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000003525000 nid=0x2f60 waiting on condition 
[0x0000000000000000]
 java.lang.Thread.State: RUNNABLE
"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting for monitor entry 
[0x000000001f54f000]
 java.lang.Thread.State: BLOCKED (on object monitor)
 at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)
 - waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)
 - locked <0x000000076b5bf1d0> (a java.lang.Object)
 at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)
 at java.lang.Thread.run(Thread.java:745)
"Thread-0" #11 prio=5 os_prio=0 tid=0x000000001eb68800 nid=0x1b28 waiting for monitor entry 
[0x000000001f44f000]
 java.lang.Thread.State: BLOCKED (on object monitor)
 at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)
 - waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)
 - locked <0x000000076b5bf1c0> (a java.lang.Object)
 at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)
 at java.lang.Thread.run(Thread.java:745)
 
// 略去部分输出
Found one Java-level deadlock:
=============================
"Thread-1":
 waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object),
 which is held by "Thread-0"
"Thread-0":
 waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object),
 which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
 at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)
 - waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)
 - locked <0x000000076b5bf1d0> (a java.lang.Object)
 at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)
 at java.lang.Thread.run(Thread.java:745)
"Thread-0":
 at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)
 - waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)
 - locked <0x000000076b5bf1c0> (a java.lang.Object)
 at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)
 at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
        避免死锁要注意加锁顺序
        另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top - Hp 进程 id 来定位是哪个线程,最后再用 jstack 排查

饥饿

        一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束。

ReentrantLock

        相对于 synchronized 它具备如下特点
                可中断
                可以设置超时时间
                可以设置为公平锁
                支持多个条件变量
                与 synchronized 一样,都支持可重入
        基本语法:
// 获取锁
reentrantLock.lock();
try {
     // 临界区
} finally {
     // 释放锁
     reentrantLock.unlock();
}
        可重入
        可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。
        如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
    method1();
}
public static void method1() {
    lock.lock();
    try {
        log.debug("execute method1");
        method2();
    } finally {
         lock.unlock();
    }
}
public static void method2() {
     lock.lock();
     try {
         log.debug("execute method2");
         method3();
     } finally {
         lock.unlock();
     }
}
public static void method3() {
     lock.lock();
     try {
         log.debug("execute method3");
     } finally {
         lock.unlock();
     }
}
        输出
17:59:11.862 [main] c.TestReentrant - execute method1 
17:59:11.865 [main] c.TestReentrant - execute method2 
17:59:11.865 [main] c.TestReentrant - execute method3

        可打断

        和单纯的lock()方法不同,线程调用了lockInterrupt()方法之后,可以"响应中断"式地加锁。

        示例:
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
     log.debug("启动...");
     try {
         lock.lockInterruptibly();
     } catch (InterruptedException e) {
         e.printStackTrace();
         log.debug("等锁的过程中被打断");
         return;
     }
     try {
         log.debug("获得了锁");
     } finally {
         lock.unlock();
     }
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
     sleep(1);
     t1.interrupt();
     log.debug("执行打断");
 } finally {
     lock.unlock();
}
        输出
18:02:40.520 [main] c.TestInterrupt - 获得了锁
18:02:40.524 [t1] c.TestInterrupt - 启动... 
18:02:41.530 [main] c.TestInterrupt - 执行打断
java.lang.InterruptedException  at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)  at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) 
 at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) 
 at cn.itcast.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17) 
 at java.lang.Thread.run(Thread.java:748) 
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断
        注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断。
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
     log.debug("启动...");
     lock.lock();
     try {
         log.debug("获得了锁");
     } finally {
         lock.unlock();
     }
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
     sleep(1);
     t1.interrupt();
     log.debug("执行打断");
     sleep(1);
} finally {
     log.debug("释放了锁");
     lock.unlock();
}
        输出
18:06:56.261 [main] c.TestInterrupt - 获得了锁
18:06:56.265 [t1] c.TestInterrupt - 启动... 
18:06:57.266 [main] c.TestInterrupt - 执行打断 // 这时 t1 并没有被真正打断, 而是仍继续等待锁
18:06:58.267 [main] c.TestInterrupt - 释放了锁
18:06:58.267 [t1] c.TestInterrupt - 获得了锁
        锁超时
        立刻失败
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
     log.debug("启动...");
     if (!lock.tryLock()) {
         log.debug("获取立刻失败,返回");
         return;
     }
     try {
         log.debug("获得了锁");
     } finally {
         lock.unlock();
     }
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
     sleep(2);
} finally {
     lock.unlock();
}
        超时失败
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
 log.debug("启动...");
 try {
 if (!lock.tryLock(1, TimeUnit.SECONDS)) {
 log.debug("获取等待 1s 后失败,返回");
 return;
 }
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 try {
 log.debug("获得了锁");
 } finally {
 lock.unlock();
 }
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
 sleep(2);
} finally {
 lock.unlock();
}
        公平锁
        ReentrantLock 默认是不公平的
ReentrantLock lock = new ReentrantLock(false);
lock.lock();
for (int i = 0; i < 500; i++) {
 new Thread(() -> {
 lock.lock();
 try {
 System.out.println(Thread.currentThread().getName() + " running...");
 } finally {
 lock.unlock();
 }
 }, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
new Thread(() -> {
 System.out.println(Thread.currentThread().getName() + " start...");
 lock.lock();
 try {
 System.out.println(Thread.currentThread().getName() + " running...");
 } finally {
 lock.unlock();
 }
}, "强行插入").start();
lock.unlock();
        强行插入,有机会在中间输出
        改为公平锁后
ReentrantLock lock = new ReentrantLock(true);
        强行插入,总是在最后输出
t465 running... 
t464 running... 
t477 running... 
t442 running... 
t468 running... 
t493 running... 
t482 running... 
t485 running... 
t481 running... 
强行插入 running...
        公平锁一般没有必要,会降低并发度。
        条件变量
        ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比 synchronized 是那些不满足条件的线程都在一间休息室等消息
        而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤 醒
        使用要点:
                await 前需要获得锁
                await 执行后,会释放锁,进入 conditionObject 等待
                await 的线程被唤醒(或打断、或超时)取重新竞争 lock
                竞争 lock 锁成功后,从 await 后继续执行
        例子:
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) {
 new Thread(() -> {
 try {
 lock.lock();
 while (!hasCigrette) {
 try {
 waitCigaretteQueue.await();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 log.debug("等到了它的烟");
 } finally {
 lock.unlock();
 }
 }).start();
 new Thread(() -> {
 try {
 lock.lock();
 while (!hasBreakfast) {
 try {
 waitbreakfastQueue.await();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 log.debug("等到了它的早餐");
 } finally {
 lock.unlock();
 }
 }).start();
 sleep(1);
 sendBreakfast();
 sleep(1);
 sendCigarette();
}
private static void sendCigarette() {
 lock.lock();
 try {
 log.debug("送烟来了");
 hasCigrette = true;
 waitCigaretteQueue.signal();
 } finally {
 lock.unlock();
 }
}
private static void sendBreakfast() {
 lock.lock();
 try {
 log.debug("送早餐来了");
 hasBreakfast = true;
 waitbreakfastQueue.signal();
 } finally {
 lock.unlock();
 }
}

        输出:

18:52:27.680 [main] c.TestCondition - 送早餐来了
18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐
18:52:28.683 [main] c.TestCondition - 送烟来了
18:52:28.683 [Thread-0] c.TestCondition - 等到了它的烟

​​​​​​

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

掌握这个Jenkins插件,离测试开发又近一步!

Jenkins Pipeline是一种可编程的、可扩展的持续交付管道&#xff0c;允许您使用脚本来定义整个软件交付过程。 以下是使用Jenkins Pipeline创建和配置流水线的基本步骤。 Part 01. 创建一个Pipeline Job 在Jenkins中创建一个新的"Pipeline"类型的Job。 以下是在J…

解决宝塔Nginx和phpMyAdmin配置端口冲突问题

问题描述 在对基于宝塔面板的 Nginx 配置文件进行端口修改时&#xff0c;我注意到 phpMyAdmin 的端口配置似乎也随之发生了变化&#xff01; 解决方法 官方建议在处理 Nginx 配置时&#xff0c;应避免直接修改默认的配置文件&#xff0c;以确保系统的稳定性和简化后续的维护…

简单问题汇总

一、vector和list 1.vector vector是可变大小数组的序列容器&#xff0c;拥有一段连续的内存空间&#xff0c;并且起始地址不变&#xff0c;因此能高效的进行随机存取&#xff0c;时间复杂度为o(1)&#xff1b;但因为内存空间是连续的&#xff0c;所以在进行插入和删除操作时…

触摸OpenNJet,云原生世界触手可及

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 导言OpenNJet云原生引擎介绍云原生平台的介绍优化与创新 为什么选择OpenNJet云原生引擎如何在windo…

【MATLAB源码-第207期】基于matlab的单相光伏并网系统仿真,并网策略采用基于扰动观测法的MPPT模型和使用电压电流双闭环SPWM控制。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 本文将重点分析光伏发电最大功率点跟踪&#xff08;MPPT&#xff09;技术和逆变器的并网控制技术&#xff0c;并在Simulink环境下建立模拟系统&#xff0c;以体现这些技术的应用与效果。文章结构如下&#xff1a;首先简介光伏…

class常量池、运行时常量池和字符串常量池的关系

类常量池、运行时常量池和字符串常量池这三种常量池&#xff0c;在Java中扮演着不同但又相互关联的角色。理解它们之间的关系&#xff0c;有助于深入理解Java虚拟机&#xff08;JVM&#xff09;的内部工作机制&#xff0c;尤其是在类加载、内存分配和字符串处理方面。 类常量池…

【java9】java9新特性概述

经过4次的跳票&#xff0c;历经曲折的Java9最终在2017年9月21日发布。因为里面加入的模块化系统&#xff0c;在最初设想的时候并没有想过那么复杂&#xff0c;花费的时间超出预估时间。距离java8大约三年时间。 Java9提供了超过150项新功能特性&#xff0c;包括备受期待的模块…

sql注入---sqli靶场

1.什么是SQL注入 SQL注入是比较常见的网络攻击方式之一&#xff0c;它不是利用操作系统的BUG来实现攻击&#xff0c;而是针对程序员编写时的疏忽&#xff0c;通过SQL语句&#xff0c;实现无账号登录&#xff0c;甚至篡改数据库 2.sql注入原理 攻击者注入一段包含注释符的SQL语…

软件2班20240513

第三次作业 package com.yanyu;import java.sql.*; import java.util.ResourceBundle;public class JDBCTest01 {public static void main(String[] args) {ResourceBundle bundle ResourceBundle.getBundle("com/resources/db");// ctrl alt vString driver …

WebSocket建立网络连接——小案例

WebSocket是一种实现全双工通信的网络技术标准&#xff0c;它允许在用户的浏览器和服务器之间进行持久的、双向的通信。以下是对WebSocket的具体介绍&#xff1a; 实时性&#xff1a;与传统HTTP请求相比&#xff0c;WebSocket提供了更高效的实时数据交换方式。一旦建立连接&am…

极限基本思想

极限基本思想 在高等数学中极限是微积分的前置思想&#xff0c;没有极限的概念&#xff0c;那么微积分的理论将不复存在。极限也用于分析一个函数的连续性&#xff0c;可以说理解极限后理解函数的连续问题是轻而易举的事情。对于函数的连续性&#xff0c;不是什么高深的词汇就…

点云分割论文阅读01--FusionVision

FusionVision: A Comprehensive Approach of 3D Object Reconstruction and Segmentation from RGB-D Cameras Using YOLO and Fast Segment Anything FusionVision&#xff1a;使用 YOLO 和 Fast Segment Anything 从 RGB-D 相机重建和分割 3D 对象的综合方法 toread&#x…

linux笔记5--shell命令2

文章目录 一. linux中的任务管理1. 图形界面2. 命令① top命令② grep命令③ ps命令补充&#xff1a; ④ kill命令图形界面杀死进程 二. 挂载(硬盘方面最重要的一个知识点)1. 什么是挂载2. 关于挂载目录① Windows② linux查看硬件分区情况(/dev下)&#xff1a;更改挂载目录结束…

MySQL—子查询

目录 ▐ 子查询概述 ▐ 准备工作 ▐ 标量子查询 ▐ 列子查询 ▐ 表子查询 ▐ 多信息嵌套 ▐ 子查询概述 • 子查询也称嵌套查询&#xff0c;即在一个查询语句中又出现了查询语句 • 子查询可以出现在from 后面 或where后面 • 出现在 from 后称表子查询&#xff0c;结…

【线程创建】——三种方式➕多线程案例练习

02 线程创建 Thread , Runnable , Callable 三种创建方式 Thread class - 继承Thread类 (重点) Runnable接口 - 实现Runnable接口 (重点) Callable接口 - 实现Callable接口 (了解) Thread 类实现 它继承了老祖宗 Object java.lang.Object java.lang.Thread 它实现了 Runnab…

DEV--C++小游戏(吃星星(0.5))

目录 吃星星&#xff08;0.5&#xff09; 该版本简介 DEV--C小游戏(吃星星(0.1)) DEV--C小游戏(吃星星(0.2)) 分部代码 头文件 命名空间变量&#xff08;增&#xff09; 副函数&#xff08;新&#xff0c;增&#xff09; 清屏函数 打印地图函数&#xff08;增&…

小红薯视频作品一键克隆,解放双手自动搬运【永久脚本+使用教程】

软件介绍&#xff1a; 小红薯作品搬运神器&#xff0c;软件只需要复制对方的作品链接即可一键克隆搬运到自己的小红书上&#xff0c;再也不用手动去复制粘贴了&#xff0c;批量起号搬运必备神器 设备需求&#xff1a; 电脑 链接&#xff1a;https://pan.baidu.com/s/11MzBqER…

15集合的应用

集合的概念 集合是一个容器&#xff0c;可以容纳其他类型的数据&#xff0c;前面所讲的数组就是一个集合。 所有的集合相关的类和接口都在java.util包下 特点 集合不能直接存储基本数据类型(但是代码上不需要体现&#xff0c;因为Java中有自动装箱)另外集合不能直接存储Java…

数据结构-栈的讲解

栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底&#xff08;因为先进后出&#xff09;。栈中的数据元素遵守后进先出LIFO&#xff08;Last In Firs…

Python GraphQL服务器实现库之tartiflette使用详解

概要 Tartiflette是一个为Python编写的GraphQL服务器实现,它建立在现代异步编程库如asyncio之上,提供了高性能的GraphQL执行环境。Tartiflette专注于提供最佳的开发者体验,支持最新的GraphQL特性。 安装 安装Tartiflette相对简单,但需要依赖于一些系统级的库。 首先,需…