Synchronized与Java线程的关系

news2025/1/24 2:31:20

前言

​ Java多线程处理任务时,为了线程安全,通常会对共享资源进行加锁,拿到锁的线程才能进行访问共享资源。而加锁方式通过都是Synchronized锁或者Lock锁。

​ 那么多线程在协同工作的时候,线程状态的变化都与锁对象有关系。

Synchronized锁

​ Java采用synchronized关键字、以互斥同步的方式的解决线程安全问题。一般Synchronized主要用于同步代码块、实例方法、静态方法。

一、使用方式 字节码分析

// 使用
public synchronized void test1(){
}
public void test2(){
    synchronized(new Test()){
    }
}
public static synchronized void test3(){
}

//反编译
public synchronized void test1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED    // here

  public void test2();
    descriptor: ()
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class com/easy/helloworld/Test
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: dup
         8: astore_1
         9: monitorenter                   // here
        10: aload_1
        11: monitorexit                    // here
        12: goto          20
        15: astore_2
        16: aload_1
        17: monitorexit                    // here
        18: aload_2
        19: athrow
        20: return

  public static synchronized void test3();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED   // here

​ 由上可知,同步代码:通过moniterenter、moniterexit 关联到到一个monitor对象,进入时设置Owner为当前线程,计数+1、退出-1。除了正常出口的 monitorexit,还在异常处理代码里插入了 monitorexit。

​ 而实例方法、静态方式是隐式调用moniterenter、moniterexit。

二、Moniterenter、Moniterexit

​ monitorenter和monitorexit这两个jvm指令,主要是基于 Mark WordObject monitor来实现的。

​ 在 JVM 中,对象在内存中分为三块区域:

  • 对象头:由Mark WordKlass Point构成。

    • Mark Word(标记字段):用于存储对象自身的运行时数据,例如存储对象的HashCode,分代年龄、锁标志位等信息,是synchronized实现轻量级锁和偏向锁的关键。 64位JVM的Mark Word组成如下:

      img

    • Klass Point(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

  • 实例数据:这部分主要是存放类的数据信息,父类的信息。

  • 字节对齐:为了内存的IO性能,JVM要求对象起始地址必须是8字节的整数倍。对于不对齐的对象,需要填充数据进行对齐。

​ 在JDK 1.6之前,synchronized只有传统的锁机制,直接关联到monitor对象,存在性能上的瓶颈。在JDK 1.6后,为了提高锁的获取与释放效率,JVM引入了两种锁机制:偏向锁和轻量级锁。它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。这几种锁的实现和转换正是依靠对象头中的Mark Word

Synchronized锁机制

1、偏向锁

1)引入偏向锁的初衷

​ 在没有多线程竞争的情况下,尽量减少不必要的轻量级锁的执行。轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只依赖一次CAS原子指令。

​ 但在多线程竞争时,需要进行偏向锁撤销步骤,因此其撤销的开销必须小于节省下来的CAS开销,否则偏向锁并不能带来收益。JDK 1.6中默认开启偏向锁,可以通过-XX:-UseBiasedLocking来禁用偏向锁。在JDK15中,已经默认禁用偏向锁了。

2)关键字

prototype_header:JVM中的每个类有一个类似mark wordprototype_header用来标记该class的epoch和偏向开关等信息

匿名偏向状态:锁对象mark word标志位为101,且存储的Thread ID为空时的状态(即锁对象为偏向锁,且没有线程偏向于这个锁对象)。

Atomic::cmpxchg_ptrCAS函数。这个方法有三个参数,依次为exchange_valuedestcompare_value。如果dest的值为compare_value则更新为exchange_value,并返回compare_value。否则,不更新并返回实际原值

3)偏向锁流程

步骤 1、从当前线程的栈中找到一个空闲的Lock Record,并指向当前锁对象。

步骤 2、获取对象的markOop数据mark,即对象头的Mark Word;

步骤 3、判断锁对象的mark word是否是偏向模式,即低3位是否为101。若不是,进入步骤4。若是,计算anticipated_bias_locking_value,判断偏向状态:

步骤 3.1anticipated_bias_locking_value若为0,代表**偏向的线程是当前线程,且mark word的epoch等于class的epoch,**这种情况下直接执行同步代码块,什么都不用做。

步骤 3.2判断class的prototype_header是否为非偏向模式。若为非偏向模式,CAS尝试将对象恢复为无锁状态。无论cas是否成功都会进入轻量级锁逻辑。

步骤 3.3、**如果epoch偏向时间戳已过期,则需要重偏向。**利用CAS指令将锁对象的mark word替换为一个偏向当前线程且epoch为类的epoch的新的mark word

步骤 3.4CAS将偏向线程改为当前线程后,如果当前是匿名偏向(即对象头中的bit field存储的Thread ID为空)且无并发冲突,则能修改成功获取偏向锁,否则进入锁升级的逻辑。

步骤 4、走到一步会进行轻量级锁逻辑。构造一个无锁状态的mark word,然后存储到Lock Record

设置为无锁状态的原因是:轻量级锁解锁时是将对象头的mark wordcas替换为Lock Record中的Displaced Mark Word,所以设置为无锁状态。如果是锁重入,则将Lock RecordDisplaced Mark Word设置为null,放到栈帧中,起到计数作用。

注意

只有匿名偏向的对象才能进入偏向锁模式。JVM启动时会延时初始化偏向锁,默认是4000ms。初始化后会将所有加载的Klass的prototype header修改为匿名偏向样式。当创建一个对象时,会通过Klass的prototype_header来初始化该对象的对象头。

​ 简单的说,偏向锁初始化结束后,后续所有对象的对象头都为匿名偏向样式,在此之前创建的对象则为无锁状态。而对于无锁状态的锁对象,如果有竞争,会直接进入到轻量级锁。这也是为什么JVM启动前4秒对象会直接进入到轻量级锁的原因。

​ 那么为什么要延迟初始化?JVM启动时必不可免会有大量sync的操作,而偏向锁并不是都有利。如果开启了偏向锁,会发生大量锁撤销和锁升级操作,大大降低JVM启动效率。

4)偏向锁的撤销

偏向锁的 撤销(revoke)是一个很特殊的操作,为了执行撤销操作,需要等待全局安全点,此时所有的工作线程都停止了执行。

偏向锁的撤销操作并不是将对象恢复到无锁可偏向的状态,而是在偏向锁的获取过程中,发现可能存在竞争时,直接将一个被偏向的对象升级到被加了轻量级锁的状态。

场景说明:

​ 锁已经偏向线程A,此时线程B尝试获取锁。这种情况下会走到Mark标记的分支。

​ 如果需要撤销的是当前线程,只要遍历当前线程的栈就能拿到lock record,可以直接调用revoke_bias,不需要等到safe point再撤销。**在调用Object#hashcode时,也会走到该分支将为偏向锁的锁对象直接恢复为无锁状态。**若不是当前线程,会被push到VM Thread中等到safe point的时候再执行。

​ VMThread内部维护了一个VMOperationQueue类型的队列,用于保存内部提交的VM线程操作VM_operation。GC、偏向锁的撤销等操作都是在这里被执行。

撤销流程:

**步骤 1、查看偏向的线程是否存活,如果已经死亡,则直接撤销偏向锁。**JVM维护了一个集合存放所有存活的线程,通过遍历该集合判断某个线程是否存活。

步骤 2、偏向的线程是否还在同步块中,如果不在,则撤销偏向锁。如果在同步块中,执行步骤3。

​ 而是否在同步块的判断依据偏向锁的重入计数方式:在偏向锁的获取中,每次进入同步块的时候都会在栈中找到第一个可用(即栈中最高的)的Lock Record,将其obj字段指向锁对象。每次解锁的时候都会把最低的Lock Record移除掉,所以可以通过遍历线程栈中的Lock Record来判断是否还在同步块中。轻量级锁的重入也是基于Lock Record的计数来判断。

步骤 3、升级为轻量级锁。将偏向线程所有相关Lock RecordDisplaced Mark Word设置为null,再将最高位的Lock RecordDisplaced Mark Word 设置为无锁状态,然后将对象头指向最高位的Lock Record。这里没有用到CAS指令,因为是在safepoint,可以直接升级成轻量级锁。

小结

​ 当只有一个线程反复进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获得锁时,就需要等到safe point时将偏向锁撤销为无锁状态或升级为轻量级/重量级锁。

​ 因此,JVM中增加了一种批量重偏向/撤销的机制以减少锁撤销的开销,而mark word中的epoch也是在这里被大量应用。但无论怎么优化,偏向锁的撤销仍有一定不可避免的成本。如果业务场景存在大量多线程竞争,那偏向锁的存在不仅不能提高性能,而且会导致性能下降。

总结

如果当前锁已偏向其他线程||epoch值过期||class偏向模式关闭||获取偏向锁的过程中存在并发冲突,都会进入到锁膨胀InterpreterRuntime::monitorenter方法, 在该方法中会进行偏向锁撤销和升级。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2dzNki4f-1692344710003)(https://tech.youzan.com/content/images/2021/07/—.svg)]

2)轻量级锁

目的:

​ 在多线程交替执行同步块的情况下,尽量避免重量级锁使用的操作系统互斥量带来的开销。但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁。

​ 在偏向锁逻辑中,cas失败会执行到InterpreterRuntime::monitorenter。在轻量级锁逻辑中,如果当前线程不是轻量级锁的重入,也会执行到InterpreterRuntime::monitorenter

​ 其内部函数fast_enter的流程,主要逻辑为revoke_and_rebias:如果当前是偏向模式且偏向的线程还在使用锁,会将锁的mark word改为轻量级锁的状态,并将偏向的线程栈中的Lock Record修改为轻量级锁对应的形式(此时Lock Record是无锁状态),且返回值不是BIAS_REVOKED_AND_REBIASED,会继续执行slow_enter

slow_enter流程步骤

步骤 1markOop mark = obj->mark()方法获取对象的markOop数据mark;

步骤 2mark->is_neutral()方法判断mark是否为无锁状态,标识位001

步骤 3、如果mark处于无锁状态,把mark保存到BasicLock对象(Lock Record的属性)的displaced_header字段;

步骤 3.1、通过CAS尝试将Mark Word更新为指向BasicLock对象的指针,如果更新成功,表示竞争到锁,则执行同步代码,否则执行步骤4;

步骤 4、如果是重入,则设置Displaced Mark Word为null。

步骤 5、到这说明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁;

轻量级锁的释放

​ 轻量级锁释放时需要将Displaced Mark Word替换回对象头的mark word中。如果CAS失败或者是重量级锁则进入到InterpreterRuntime::monitorexit方法中。monitorexit直接调用slow_exit方法释放Lock Record

最后执行的是如果是fast_exit方法,且是轻量级锁,尝试cas替换mark word。若解锁时有竞争,会调用inflate方法进行重量级锁膨胀,升级到到重量级锁后再执行exit方法。

3)重量级锁

​ 重量级锁通过对象内部的监视器(monitor)实现,其依赖于底层操作系统的Mutex Lock实现,需要额外的用户态到内核态切换的开销。由上文分析,slow_enter获取轻量级锁未成功时,会在inflate中完成锁膨胀。

​ 而inflate其中是一个for循环,主要是为了处理多线程同时调用inflate的情况。然后会根据锁对象的状态进行不同的处理:

1.已经是重量级状态,说明膨胀已经完成,返回并继续执行ObjectMonitor::enter方法。
2.如果是轻量级锁则需要进行膨胀操作。
3.如果是膨胀中状态,则进行忙等待。
4.如果是无锁状态则需要进行膨胀操作。

步骤过程

步骤 1、调用omAlloc获取一个可用的ObjectMonitor对象。在omAlloc方法中会先从线程私有monitor集合omFreeList中分配对象,如果omFreeList中已经没有monitor对象,则从JVM全局gFreeList中分配一批monitoromFreeList中;

步骤 2、通过CAS尝试将Mark Word设置为markOopDesc:INFLATING,标识当前锁正在膨胀中。如果CAS失败,说明同一时刻其它线程已经将Mark Word设置为markOopDesc:INFLATING,当前线程进行自旋等待膨胀完成。

步骤 3、如果CAS成功,设置monitor的各个字段:设置monitor的header字段为displaced mark word,owner字段为Lock Record,obj字段为锁对象等;

步骤 4、设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象;

monitor竞争

​ 当锁膨胀inflate执行完并返回对应的ObjectMonitor时,并不表示该线程竞争到了锁,真正的锁竞争发生在ObjectMonitor::enter方法中。

monitor等待

ObjectMonitor竞争失败的线程,通过自旋执行ObjectMonitor::EnterI方法等待锁的释放。

EnterI大致原理

一个ObjectMonitor对象包括两个同步队列(_cxq_EntryList) ,以及一个等待队列_WaitSet。cxq、EntryList 、WaitSet都是由ObjectWaiter构成的链表结构。其中,_cxq为单向链表,_EntryList为双向链表。

​ 当一个线程尝试获得重量级锁且没有竞争到时,该线程会被封装成一个ObjectWaiter对象插入到cxq的队列的队首,然后调用park函数挂起当前线程,进入BLOCKED状态。

​ 当线程释放锁时,会根据唤醒策略,从cxq或EntryList中挑选一个线程unpark唤醒。如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,进入WAITING或TIMED_WAITING状态。

​ 当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去,进入BLOCKED状态。需要注意的是,当调用一个锁对象的waitnotify方法时,若当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。

monitor释放

​ 当某个持有锁的线程执行完同步代码块时,会进行锁的释放。在HotSpot中,通过退出monitor的方式实现锁的释放,并通知被阻塞的线程,具体实现位于ObjectMonitor::exit方法中。

Java线程

1、线程的实现

1)内核线程实现

内核线程(Kernel-Level Thread,KLT):由内核来完成线程切换,内核通过调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。 程序一般不会直接去使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),也就是通常意义上的线程。

优点:每个LWP都是独立的调度单元。一个LWP被阻塞,不影响其他LWP。

缺点:基于KLT,耗资源。线程的创建、析构、同步都需要进行系统调用,频繁的用户态、内核态切换。

2)用户线程实现(User Thread,UT)

广义:非内核线程,都可认为是用户线程。(包括LWT,虽然LWT的大多操作都要映射到KLT)

狭义:完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。UT也只感知到掌管这些UT的进程P。

优点:用户线程的创建、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。

缺点:线程的创建、销毁、切换和调度都是用户必须考虑到问题。“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难。

3)混合模式。即存在用户线程,也存在轻量级进程

​ 用户线程的创建、切换、析构等操作依然廉价,可以支持大规模的用户线程并发,且可以使用内核线程提供的线程调度功能及处理器映射。

​ 线程的实现依赖操作系统支持的线程模型。在主流的操作系统上,hotspot、classic、art等虚拟机默认是 1:1的线程模型。在Solaris平台上,hotspot支持1:1、N:M两种线程模型。

2、线程的转换

​ 线程的状态,指的是Thread 类中threadStatus的值。

​ 创建Thread 类的对象,才能谈其状态。这个时候,线程t就处于新建状态。但他还不是“线程”。**调用start()后,会执行一个native方法创建内核线程。 这时候才有一个真正的线程创建出来,并即刻开始运行。这个内核线程与线程t进行1:1的映射。**这时候t具备运行能力,进入RUNNABLE状态。 RUNNABLE可以细分为READY和RUNNING,两者的区别只是是否等待到了资源并开始运行。

​ 处于RUNNABLE且未运行的线程,会进入一个就绪队列中,等待操作系统的调度。处于就绪队列的线程都在等待资源,这个资源可以是cpu的时间片、也可以是系统的IO。

3、线程相关方法

1)wait

​ 通过object获得objectMonitor,将Thread封装成OjectWaiter对象,然后addWaiter将它插入waitSet中,进入waiting或timed_waiting状态。最后释放锁,并通过底层的park方法挂起线程;

2)notify

​ 通过object获得objectMonitor,调用objectMonitor的notify方法。这个notify最后会走到ObjectMonitor::DequeueWaiter方法,获取waitSet列表中的第一个ObjectWaiter节点。并根据不同的策略,将取出来的ObjectWaiter节点,加入到EntryListcxq中。 notifyAll的实现类似于notify,主要差别在多了个for循环。

notifynotifyAll并不会立即释放所占有的ObjectMonitor对象,其真正释放ObjectMonitor的时间点是在执行monitorexit指令。

一旦释放ObjectMonitor对象了,entryListcxq中的ObjectWaiter节点会依据QMode所配置的策略,通过ExitEpilog方法唤醒取出来的ObjectWaiter节点。被唤醒的线程,继续参与monitor的竞争。若竞争失败,重新进入BLOCKED状态

3)join

join的本质仍然是 wait() 方法。在使用join时,JVM会帮我们隐式调用notify,因此我们不需要主动notify唤醒主线程。 而sleep()方法最终是调用SleepEvent对象的park方法

4)sleep

Thread.sleep在jvm层面上是调用thread中SleepEvent对象的park()方法实现阻塞线程,在此过程中会通过判断时间戳来决定线程的睡眠时间是否达到了指定的毫秒。

5)park

parkunpark方法也与同步语义无关。每个线程都与一个许可(permit)关联。unpark函数为线程提供permit,线程调用park函数则等待并消耗permit。

小结

​ 1、 JVM线程状态不代表内核线程状态。

​ 2、BLOCKED的线程一定处于entryList或cxq中,而处于WAITING和TIMED WAITING的线程,可能是由于执行了sleep或park进入该状态,不一定在waitSet中。也就是说,处于BLOCKED状态的线程一定是与同步相关。由这可延伸出,调用 jdk 的 lock并获取不到锁的线程,进入的是 WAITING 或 TIMED_WAITING 状态,而不是BLOCKED状态。

总结

在这里插入图片描述

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

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

相关文章

安防监控视频云存储平台EasyNVR出现内核报错的情况该如何解决?

安防视频监控汇聚EasyNVR视频集中存储平台&#xff0c;是基于RTSP/Onvif协议的安防视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等格式。 近期有用户联系到我们&#xff0c;EasyNVR…

lab3 pgtbl

Pre 在这个lab中&#xff0c;你将探索页表&#xff0c;并且修改它们以简化从用户空间拷贝数据到内核空间的函数 在开始之前&#xff0c;需要完成 阅读xv6 book的第3章kern/memlayout.h 有关内存的布局kern/vm.c 包含大部分虚拟内存的代码kernel/kalloc.c 分配和释放虚拟内存的代…

Redisson实现分布式锁示例

一、引入依赖 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.0</version></dependency>二、配置类 import org.redisson.Redisson; import org.redisson.api.RedissonClient;…

无涯教程-Perl - wantarray函数

描述 如果当前正在执行的函数的context正在寻找列表值,则此函数返回true。在标量context中返回false。 语法 以下是此函数的简单语法- wantarray返回值 如果没有context,则此函数返回undef&#xff1b;如果lvalue需要标量,则该函数返回0。 例 以下是显示其基本用法的示例…

调整mysql 最大传输数据 max_allowed_packet=500M

查看 -- show VARIABLES like %max_allowed_packet%; -- set global max_allowed_packet 1024*1024*64;-- show variables like %timeout%; -- show global status like com_kill; show global variables like max_allowed_packet; -- set global max_allowed_packet1024*102…

机器学习|DBSCAN 算法的数学原理及代码解析

机器学习&#xff5c;DBSCAN 算法的数学原理及代码解析 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的聚类算法&#xff0c;它能够有效地发现任意形状的聚类簇&#xff0c;并且可以识别出噪声点。在本文中&…

NineData通过AWS FTR认证,打造安全可靠的数据管理平台

近日&#xff0c;NineData 作为新一代的云原生智能数据管理平台&#xff0c;成功通过了 AWS&#xff08;Amazon Web Service&#xff09;的 FTR 认证。NineData 在 FTR 认证过程中表现出色&#xff0c;成功通过了各项严格的测试和评估&#xff0c;在数据安全管理、技术应用、流…

element时间选择器el-date-picter使用disabledDate指定禁用的日期

需要的效果 <el-date-pickerclass"selectstyle"v-model"year"value-format"yyyy"type"year":picker-options"disabledCli"placeholder"选择年"> </el-date-picker>data() {return {disabledCli: {/…

PostgreSQL中的密码验证方法

假设您想在客户端/服务器协议中实现密码身份验证方法。 您将如何做到这一点以及可能出现的问题是什么&#xff1f; 以下是 PostgreSQL 中如何完成此操作的故事。 password 一开始&#xff0c;PostgreSQL 只有 pg_hba.conf 中现在称为“password”的方法。 这是你能想象到的最…

数字化施工:解决传统施工难题,提高施工效率和质量的行业革命

建筑行业是我国国民经济的重要组成部分&#xff0c;也是支柱性产业之一。然而&#xff0c;建筑业同时也是一个安全事故多发的高风险行业。如何加强施工现场的安全管理&#xff0c;降低事故发生的频率&#xff0c;避免各种违规操作和不文明施工&#xff0c;提高建筑工程的质量&a…

web即时通讯系统与APP即时通讯系统有什么区别?

随着互联网的不断发展&#xff0c;即时通讯技术也在不断地完善和发展&#xff0c;其中Web即时通讯系统和APP即时通讯系统成为了人们广泛使用的两种通讯方式。那么&#xff0c;这两者之间究竟有什么区别呢&#xff1f;在本文中&#xff0c;我们将为您详细介绍这两种通讯方式的区…

“RFID与光伏板的完美融合:探索能源科技的新时代!“

随着科技的不断发展&#xff0c;人类创造出了许多令人惊叹的发明。其中&#xff0c;RFID&#xff08;Radio Frequency Identification&#xff09;技术的应用在各个领域日益广泛。最近的研究表明&#xff0c;将RFID技术应用于光伏板领域&#xff0c;不仅可以提高光伏板的效率&a…

BY133 整流二极管 1300V 1A DO-41

BY133是什么类型的二极管&#xff1f;BY133厂家&#xff0c;哪家厂家在生产&#xff1f;BY133厂家哪家好&#xff1f;二极管BY133参数怎么看&#xff1f;BY133二极管报价&#xff0c;价格多少&#xff1f;二极管BY133哪家供应商有现货……对于一枚电子元器件而言&#xff0c;其…

Android2:构建交互式应用

一。创建项目 项目名Beer Adviser 二。更新布局 activity_main.xml <?xml version"1.0" encoding"utf-8"?><LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"…

低代码系列——初步认识低代码

低代码系列目录 一、初步认识低代码 二、低代码是什么 三、低代码平台的概念和分类 01.无代码开发平台 02.低代码应用平台(LCAP) 03.多重体验开发平台(MXDP) 04.智能业务流程管理套件(iBPMS) 四、低代码的能力指标 五、低代码平台jnpf 表单 报表 流程 权限 一、初步认识低代码 …

正规的股票杠杆公司_杠杆公司排名(2023年版的)

本文将介绍一些正规的股票杠杆公司&#xff0c;并重点介绍配先查网站的特点&#xff0c;该网站是一家专业查询实盘杠杆平台的网站&#xff0c;提供相关信息和参考。 杠杆公司排名&#xff08;2023年版的&#xff09;&#xff1a;广盛网、一鼎盈、尚红网、盛多网、红腾网、富灯…

神卓互联内网穿透搭建云表系统

云表是什么&#xff1f; 云表系统是一种无代码开发平台&#xff0c;它具备完善的功能和各种业务级应用管理模板&#xff0c;可以帮助中小企业快速开发和定制自己的系统。通过云表系统&#xff0c;用户可以使用表格编程的开发方式&#xff0c;就像使用Excel一样简单&#xff0c…

BT利器之wazuh

目录 一、什么是wazuh 二、wazuh的安装 1.仓库安装 2.虚拟机OVA安装 3.其他安装方式 三、浅析wazuh的规则、解码器等告警原理以及主动响应 1.主动响应(active-response) 2.告警信息(alerts) 3.规则以及解码器(rules and decoders) 3.1.规则 3.2.解码器 4.linux后门r…

大疆飞卡30运载无人机技术分享

大疆飞卡30是大疆公司面向运输领域推出的一款专业运载无人机。它采用了优秀的设计,装备了多种先进传感器,以解决运输中的难题。以下我们来了解一下其主要特点: 【应用领域】 飞卡30适用于山地救灾、农业化肥施用、工程材料运送等交通不便的山区应用,也适用于海岛联通等运输链…

WinSW使用说明

使用说明 前言下载配置介绍示例jar包启动示例 安装服务 前言 由于使用windows自动的自启方法&#xff0c;不管是将程序启动服务放到开机自启文件夹中&#xff0c;还是创建任务计划程序&#xff0c;都没有很好的实现程序的开机自启效果&#xff0c;而WinSW很好的解决了这个问题…