java不同进程的相互唤醒_Java线程生命周期与状态切换

7b2391e374d230c0ca04f05132b51c5c.png

前提

最近有点懒散,没什么比较有深度的产出。刚好想重新研读一下JUC线程池的源码实现,在此之前先深入了解一下Java中的线程实现,包括线程的生命周期、状态切换以及线程的上下文切换等等。编写本文的时候,使用的JDK版本是11。

Java线程的实现

「JDK1.2之后」,Java线程模型已经确定了基于操作系统原生线程模型实现。因此,目前或者今后的JDK版本中,操作系统支持怎么样的线程模型,在很大程度上决定了Java虚拟机的线程如何映射,这一点在不同的平台上没有办法达成一致,虚拟机规范中也未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对于Java程序来说,这些差异是透明的。

对应Oracle Sun JDK或者说Oracle Sun JVM而言,它的Windows版本和Linux版本都是使用「一对一的线程模型」实现的(如下图所示)。

ef87c4cd2ba14f44a07e825262cd2a1c.png

也就是一条Java线程就映射到一条轻量级进程(「Light Weight Process」)中,而一条轻量级线程又映射到一条内核线程(「Kernel-Level Thread」)。我们平时所说的线程,往往就是指轻量级进程(或者通俗来说我们平时新建的java.lang.Thread就是轻量级进程实例的一个"句柄",因为一个java.lang.Thread实例会对应JVM里面的一个JavaThread实例,而JVM里面的JavaThread就应该理解为轻量级进程)。前面推算这个线程映射关系,可以知道,我们在应用程序中创建或者操作的java.lang.Thread实例最终会映射到系统的内核线程,如果我们恶意或者实验性无限创建java.lang.Thread实例,最终会影响系统的正常运行甚至导致系统崩溃(可以在Windows开发环境中做实验,确保内存足够的情况下使用死循环创建和运行java.lang.Thread实例)。

线程调度方式包括两种,协同式线程调度和抢占式线程调度。

线程调度方式描述劣势优势协同式线程调度线程的执行时间由线程本身控制,执行完毕后主动通知操作系统切换到另一个线程上某个线程如果不让出CPU执行时间可能会导致整个系统崩溃实现简单,没有线程同步的问题抢占式线程调度每个线程由操作系统来分配执行时间,线程的切换不由线程自身决定实现相对复杂,操作系统需要控制线程同步和切换不会出现一个线程阻塞导致系统崩溃的问题

Java线程最终会映射为系统内核原生线程,所以Java线程调度最终取决于系操作系统,而目前主流的操作系统内核线程调度基本都是使用抢占式线程调度。也就是可以死记硬背一下:「Java线程是使用抢占式线程调度方式进行线程调度的」

很多操作系统都提供线程优先级的概念,但是由于平台特性的问题,Java中的线程优先级和不同平台中系统线程优先级并不匹配,所以Java线程优先级可以仅仅理解为“「建议优先级」”,通俗来说就是java.lang.Thread#setPriority(int newPriority)并不一定生效,「有可能Java线程的优先级会被系统自行改变」

Java线程的状态切换

Java线程的状态可以从java.lang.Thread的内部枚举类java.lang.Thread$State得知:

public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}

这些状态的描述总结成图如下:

a141ad8262322aaf1ed8f4cff6431444.png

「线程状态之间关系切换」图如下:

bcf07745c0e4beaa3ae469443312e1dd.png

下面通过API注释和一些简单的代码例子分析一下Java线程的状态含义和状态切换。

NEW状态

「API注释」

/*** Thread state for a thread which has not yet started.**/
NEW,
❝线程实例尚未启动时候的线程状态。

一个刚创建而尚未启动(尚未调用Thread#start()方法)的Java线程实例的就是处于NEW状态。

public class ThreadState {public static void main(String[] args) throws Exception {Thread thread = new Thread();System.out.println(thread.getState());}
}// 输出结果
NEW

RUNNABLE状态

「API注释」

/*** Thread state for a runnable thread.  A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.*/
RUNNABLE,
❝可运行状态下线程的线程状态。可运行状态下的线程在Java虚拟机中执行,但它可能执行等待操作系统的其他资源,例如处理器。

当Java线程实例调用了Thread#start()之后,就会进入RUNNABLE状态。RUNNABLE状态可以认为包含两个子状态:READYRUNNING

  • READY:该状态的线程可以被线程调度器进行调度使之更变为RUNNING状态。
  • RUNNING:该状态表示线程正在运行,线程对象的run()方法中的代码所对应的的指令正在被CPU执行。

当Java线程实例Thread#yield()方法被调用时或者由于线程调度器的调度,线程实例的状态有可能由RUNNING转变为READY,但是从线程状态Thread#getState()获取到的状态依然是RUNNABLE。例如:

public class ThreadState1 {public static void main(String[] args) throws Exception {Thread thread = new Thread(()-> {while (true){Thread.yield();}});thread.start();Thread.sleep(2000);System.out.println(thread.getState());}
}
// 输出结果
RUNNABLE

WAITING状态

「API注释」

/*** Thread state for a waiting thread.* A thread is in the waiting state due to calling one of the* following methods:* <ul>*   <li>{@link Object#wait() Object.wait} with no timeout</li>*   <li>{@link #join() Thread.join} with no timeout</li>*   <li>{@link LockSupport#park() LockSupport.park}</li>* </ul>** <p>A thread in the waiting state is waiting for another thread to* perform a particular action.** For example, a thread that has called {@code Object.wait()}* on an object is waiting for another thread to call* {@code Object.notify()} or {@code Object.notifyAll()} on* that object. A thread that has called {@code Thread.join()}* is waiting for a specified thread to terminate.*/WAITING,
❝等待中线程的状态。一个线程进入等待状态是由于调用了下面方法之一:不带超时的Object#wait() 不带超时的Thread#join() LockSupport.park() 一个处于等待状态的线程总是在等待另一个线程进行一些特殊的处理。例如:一个线程调用了Object#wait(),那么它在等待另一个线程调用对象上的Object#notify()或者Object#notifyAll();一个线程调用了Thread#join(),那么它在等待另一个线程终结。

WAITING「无限期的等待状态」,这种状态下的线程不会被分配CPU执行时间。当一个线程执行了某些方法之后就会进入无限期等待状态,直到被显式唤醒,被唤醒后,线程状态由WAITING更变为RUNNABLE然后继续执行。

RUNNABLE转换为WAITING的方法(无限期等待)WAITING转换为RUNNABLE的方法(唤醒)Object#wait()Object#notify() | Object#notifyAll()Thread#join()-LockSupport.part()LockSupport.unpart(thread)

其中Thread#join()方法相对比较特殊,它会阻塞线程实例直到线程实例执行完毕,可以观察它的源码如下:

public final void join() throws InterruptedException {join(0);
}public final synchronized void join(long millis)throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}
}

可见Thread#join()是在线程实例存活的时候总是调用Object#wait()方法,也就是必须在线程执行完毕isAlive()为false(意味着线程生命周期已经终结)的时候才会解除阻塞。

基于WAITING状态举个例子:

public class ThreadState3 {public static void main(String[] args) throws Exception {Thread thread = new Thread(()-> {LockSupport.park();while (true){Thread.yield();}});thread.start();Thread.sleep(50);System.out.println(thread.getState());LockSupport.unpark(thread);Thread.sleep(50);System.out.println(thread.getState());}
}
// 输出结果
WAITING
RUNNABLE

TIMED WAITING状态

「API注释」

/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
*   <li>{@link #sleep Thread.sleep}</li>
*   <li>{@link Object#wait(long) Object.wait} with timeout</li>
*   <li>{@link #join(long) Thread.join} with timeout</li>
*   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
*   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
❝定义了具体等待时间的等待中线程的状态。一个线程进入该状态是由于指定了具体的超时期限调用了下面方法之一:Thread.sleep() 带超时的Object#wait() 带超时的Thread#join() LockSupport.parkNanos() LockSupport.parkUntil()

TIMED WAITING就是「有限期等待状态」,它和WAITING有点相似,这种状态下的线程不会被分配CPU执行时间,不过这种状态下的线程不需要被显式唤醒,只需要等待超时限期到达就会被VM唤醒,有点类似于现实生活中的闹钟。

RUNNABLE转换为TIMED WAITING的方法(有限期等待)TIMED WAITING转换为RUNNABLE的方法(超时解除等待)Object#wait(timeout)-Thread#sleep(timeout)-Thread#join(timeout)-LockSupport.parkNanos(timeout)-LockSupport.parkUntil(timeout)-

举个例子:

public class ThreadState4 {public static void main(String[] args) throws Exception {Thread thread = new Thread(()-> {try {Thread.sleep(1000);} catch (InterruptedException e) {//ignore}});thread.start();Thread.sleep(50);System.out.println(thread.getState());Thread.sleep(1000);System.out.println(thread.getState());}
}
// 输出结果
TIMED_WAITING
TERMINATED

BLOCKED状态

「API注释」

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
❝此状态表示一个线程正在阻塞等待获取一个监视器锁。如果线程处于阻塞状态,说明线程等待进入同步代码块或者同步方法的监视器锁或者在调用了Object#wait()之后重入同步代码块或者同步方法。

BLOCKED状态也就是阻塞状态,该状态下的线程不会被分配CPU执行时间。线程的状态为BLOCKED的时候有两种可能的情况:

❝A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method
  1. 线程正在等待一个监视器锁,只有获取监视器锁之后才能进入synchronized代码块或者synchronized方法,在此等待获取锁的过程线程都处于阻塞状态。
❝reenter a synchronized block/method after calling Object#wait()
  1. 线程X步入synchronized代码块或者synchronized方法后(此时已经释放监视器锁)调用Object#wait()方法之后进行阻塞,当接收其他线程T调用该锁对象Object#notify()/notifyAll(),但是线程T尚未退出它所在的synchronized代码块或者synchronized方法,那么线程X依然处于阻塞状态(注意API注释中的「reenter」,理解它场景2就豁然开朗)。

更加详细的描述可以参考笔者之前写过的一篇文章:深入理解Object提供的阻塞和唤醒API

针对上面的场景1举个简单的例子:

public class ThreadState6 {private static final Object MONITOR = new Object();public static void main(String[] args) throws Exception {Thread thread1 = new Thread(()-> {synchronized (MONITOR){try {Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {//ignore}}});Thread thread2 = new Thread(()-> {synchronized (MONITOR){System.out.println("thread2 got monitor lock...");}});thread1.start();Thread.sleep(50);thread2.start();Thread.sleep(50);System.out.println(thread2.getState());}
}
// 输出结果
BLOCKED

针对上面的场景2举个简单的例子:

public class ThreadState7 {private static final Object MONITOR = new Object();private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) throws Exception {System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));Thread thread1 = new Thread(() -> {synchronized (MONITOR) {System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));try {Thread.sleep(1000);MONITOR.wait();} catch (InterruptedException e) {//ignore}System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));}});Thread thread2 = new Thread(() -> {synchronized (MONITOR) {System.out.println(String.format("[%s]-thread2 got monitor lock...", F.format(LocalDateTime.now())));try {MONITOR.notify();Thread.sleep(2000);} catch (InterruptedException e) {//ignore}System.out.println(String.format("[%s]-thread2 releases monitor lock...", F.format(LocalDateTime.now())));}});thread1.start();thread2.start();// 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,确保thread1调用了Object#wait()Thread.sleep(1500);  System.out.println(thread1.getState());System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));}
}
// 某个时刻的输出如下:
[2019-06-20 00:30:22]-begin...
[2019-06-20 00:30:22]-thread1 got monitor lock...
[2019-06-20 00:30:23]-thread2 got monitor lock...
BLOCKED
[2019-06-20 00:30:23]-end...
[2019-06-20 00:30:25]-thread2 releases monitor lock...
[2019-06-20 00:30:25]-thread1 exit waiting...

场景2中:

  • 线程2调用Object#notify()后睡眠2000毫秒再退出同步代码块,释放监视器锁。
  • 线程1只睡眠了1000毫秒就调用了Object#wait(),此时它已经释放了监视器锁,所以线程2成功进入同步块,线程1处于API注释中所述的reenter a synchronized block/method的状态。
  • 主线程睡眠1500毫秒刚好可以命中线程1处于reenter状态并且打印其线程状态,刚好就是BLOCKED状态。

这三点看起来有点绕,多看几次多思考一下应该就能理解。

TERMINATED状态

「API注释」

/*** Thread state for a terminated thread.* The thread has completed execution.*/ 
TERMINATED;
❝终结的线程对应的线程状态,此时线程已经执行完毕。

TERMINATED状态表示线程已经终结。一个线程实例只能被启动一次,准确来说,只会调用一次Thread#run()方法,Thread#run()方法执行结束之后,线程状态就会更变为TERMINATED,意味着线程的生命周期已经结束。

举个简单的例子:

public class ThreadState8 {public static void main(String[] args) throws Exception {Thread thread = new Thread(() -> {});thread.start();Thread.sleep(50);System.out.println(thread.getState());}
}
// 输出结果
TERMINATED

上下文切换

多线程环境中,当一个线程的状态由RUNNABLE转换为非RUNNABLEBLOCKEDWAITING或者TIMED_WAITING)时,相应线程的上下文信息(也就是常说的Context,包括CPU的寄存器和程序计数器在某一时间点的内容等等)需要被保存,以便线程稍后恢复为RUNNABLE状态时能够在之前的执行进度的基础上继续执行。而一个线程的状态由非RUNNABLE状态进入RUNNABLE状态时可能涉及恢复之前保存的线程上下文信息并且在此基础上继续执行。这里的对「线程的上下文信息进行保存和恢复的过程」就称为上下文切换(Context Switch)。

线程的上下文切换会带来额外的性能开销,这包括保存和恢复线程上下文信息的开销、对线程进行调度的CPU时间开销以及CPU缓存内容失效的开销(线程所执行的代码从CPU缓存中访问其所需要的变量值要比从主内存(RAM)中访问响应的变量值要快得多,但是「线程上下文切换会导致相关线程所访问的CPU缓存内容失效,一般是CPU的L1 CacheL2 Cache,使得相关线程稍后被重新调度到运行时其不得不再次访问主内存中的变量以重新创建CPU缓存内容)。

Linux系统中,可以通过vmstat命令来查看全局的上下文切换的次数,例如:

$ vmstat 1

对于Java程序的运行,在Linux系统中也可以通过perf命令进行监视,例如:

$ perf stat -e cpu-clock,task-clock,cs,cache-reference,cache-misses java YourJavaClass

参考资料中提到Windows系统下可以通过自带的工具perfmon(其实也就是任务管理器)来监视线程的上下文切换,实际上笔者并没有从任务管理器发现有任何办法查看上下文切换,通过搜索之后发现了一个工具:Process Explorer。运行Process Explorer同时运行一个Java程序并且查看其状态:

a530221b28d915873d7e1c344cf83ddb.png

因为打了断点,可以看到运行中的程序的上下文切换一共7000多次,当前一秒的上下文切换增量为26(因为笔者设置了Process Explorer每秒刷新一次数据)。

监控线程状态

如果项目在生产环境中运行,不可能频繁调用Thread#getState()方法去监测线程的状态变化。JDK本身提供了一些监控线程状态的工具,还有一些开源的轻量级工具如阿里的Arthas,这里简单介绍一下。

使用jvisualvm

jvisualvm是JDK自带的堆、线程等待JVM指标监控工具,适合使用于开发和测试环境。它位于JAVA_HOME/bin目录之下。

5b42c2c3e16a1ff2250f632bfe25ca87.png

其中线程Dump的按钮类似于下面要提到的jstack命令,用于导出所有线程的栈信息。

使用jstack

jstack是JDK自带的命令行工具,功能是用于获取指定PID的Java进程的线程栈信息。例如本地运行的一个IDEA实例的PID是11376,那么只需要输入:

jstack 11376

然后控制台输出如下:

248c73612ae5fa0b4db759f3791f1759.png

另外,如果想要定位具体Java进程的PID,可以使用jps命令。

使用JMC

JMC也就是Java Mission Control,它也是JDK自带的工具,提供的功能要比jvisualvm强大,包括MBean的处理、线程栈已经状态查看、飞行记录器等等。

804d015953db9c648230eebe66a6991e.png

小结

理解Java线程状态的切换和一些监控手段,更有利于日常开发多线程程序,对于生产环境出现问题,通过监控线程的栈信息能够快速定位到问题的根本原因(通常来说,目前比较主流的MVC应用(准确来说应该是Servlet容器如Tomcat)都是通过一个线程处理一个单独的请求,当请求出现阻塞的时候,导出对应处理请求的线程基本可以定位到阻塞的精准位置,如果使用消息队列例如RabbitMQ,消费者线程出现阻塞也可以利用相似的思路解决)。

原文出自公众号:Throwable
原文链接:https://mp.weixin.qq.com/s/BAOt4zXkRGJbUliJXJQMUg

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

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

相关文章

32查运行内存的map文件_linux内存管理

概念先行先理解内存管理中的几个概念&#xff1a;内存&#xff0c;主存&#xff0c;缓存&#xff0c;外存&#xff0c;虚拟内存&#xff0c;物理内存&#xff0c;虚拟地址&#xff0c;物理地址外存:计算机的外部存储&#xff0c;比如硬盘(机械硬盘、固态硬盘、混合硬盘)&#x…

react usecontext_鬼才!我居然把 Vue3 的原理用到了 React 上?

前言vue-next是Vue3的源码仓库&#xff0c;Vue3采用lerna做package的划分&#xff0c;而响应式能力vue/reactivity被划分到了单独的一个package中。如果我们想把它集成到React中&#xff0c;可行吗&#xff1f;来试一试吧。使用示例话不多说&#xff0c;先看看怎么用的解解馋吧…

微信多开txt_在电脑上怎么实现微信多开的效果

现在越来越多的年轻人在电脑办公的时候&#xff0c;不止有一个微信号&#xff0c;由于工作、家庭等各方面因素&#xff0c;想自己能在一个电脑上打开多个微信怎么办&#xff1f;下面小编就把自己的所学分享给大家一.首先在电脑桌面上新建一个txt文本文件&#xff0c;把这个文档…

vue slot scope使用_20、slot插槽的用法

重点&#xff1a;单个插槽、具名插槽、作用域插槽的用法&#xff1b;访问插槽的方法。其实本小白对插槽理解的还不深&#xff0c;哪些场景会经常用到插槽也不了解。但是本着“大胆猜测”的理念&#xff0c;我的猜测如下&#xff1a;假设有 父组件A&#xff0c;有 子组件B、子组…

python工作目录_如何使用python 3获取当前工作目录?

When I run the following script in IDLE import os print(os.getcwd()) I get output as D:\testtool but when I run from cmd prompt, I get c:\Python33>python D:\testtool\current_dir.py c:\Python33 How do I get same result which I got using IDLE ? 解决方案 …

flutter刷新页面_用Flutter实现58App的首页

背景Flutter作为全新跨平台应用框架&#xff0c;在页面渲染和MD开发上优势明显&#xff0c;可谓是业界一枝独秀。正好最近有这样的一个机会学习Flutter开发&#xff0c;我们便尝试用它开发一个MD风格的较复杂页面&#xff0c;来比较跟原生应用开发的优势。也是想通过对新框架的…

python调用报表制作工具_使用Python轻松制作漂亮的表格

Python太有用而且很方便 图表可以用matplotlib轻松制作&#xff0c;数值计算只要有numpy就行。 最近&#xff0c;Python被广泛用于机器学习系统的研究&#xff0c;甚至还能制作游戏。 我突然想知道&#xff1a;“是否可以用Python来制作图表而不是表格&#xff1f;” 这个时候&…

小米网关控制空调伴侣_小爱同学怎么控制灯?

说说我们神奇小爱同学吧&#xff0c;小爱同学是小米旗下的一款智能AI音箱&#xff0c;会根据您的指令来操作电器设备&#xff0c;比如说开关灯&#xff0c;那么小爱同学怎么控制灯&#xff1f;如果家里的是传统的灯泡&#xff0c;不是智能灯连接还能控制吗&#xff1f;今天蜜罐…

bochs上网镜像怎么上网_【干货科普】上网慢!经常掉线!怎么办?

文章来源&#xff1a;鲜枣课堂(ID&#xff1a;xzclasscom)作为也算是懂点技术的半专业人士&#xff0c;我们放假回去&#xff0c;遇到亲友&#xff0c;很可能被问到这样的问题——“我的手机(电脑)上网经常掉线&#xff0c;是为什么&#xff1f;”“我的手机(电脑)上网总是很慢…

sql 中位数_【PL/SQL 自定义函数】 常用场景

看完这章后你会学习到以下内容&#xff1a;1.练习场景2.面试场景3.工作应用场景总览思维导图&#xff1a;面试部分&#xff1a;1.创建函数,从emp表中查询指定员工编号的职工的工资CREATE OR REPLACE FUNCTION CHECK_SAL(F_EMPNO IN EMP.EMPNO%TYPE) RETURN NUMBER ISV_SAL VARC…

swift date 计算差_[Swift 设计模式] 适配器

更多内容&#xff0c;欢迎关注公众号&#xff1a;Swift花园喜欢文章&#xff1f;不如来个 ➕三连&#xff1f;关注专栏&#xff0c;关注我 将一个不兼容的对象转换成目标接口或者类&#xff0c;这是适配器模式的作用。下面这件东西是适配器模式在现实世界中最贴切的表达。 USB-…

委外订单_听听晚报-英特尔扩大芯片委外订单、苹果秋季或举行两场发布会

英特尔扩大芯片委外订单据外媒报道称&#xff0c;美国半导体厂商英特尔已与中国台湾芯片制造厂商台积电达成协议&#xff0c;明年开始采用后者7nm的优化版本6nm制程量产处理器或显卡&#xff0c;预估投片量将达到18万片。该消息发出后&#xff0c;资本市场反应剧烈&#xff0c;…

打开另外一个页面_如何在PDF页面中插入图片?

如何给PDF添加图片&#xff1f;有些时候为了丰富PDF的文档内容&#xff0c;需要添加一些图片&#xff0c;相比Word或PPT文档可以直接插入图片&#xff0c;而PDF的操作很多人可能并不熟悉&#xff0c;下面一起来看看如何在PDF文档中插入图片。关于PDF文档插入图片分为两种情况&a…

spring boot mybatis 整合_MyBatis学习:MyBatis和Spring整合

1. 整合的工程结构首先我们来看下整合之后的工程结构是什么样的。2. 配置文件在于spring整合之前&#xff0c;mybatis都是自己管理数据源的&#xff0c;然后sqlSessionFactory是我们自己去注入的&#xff0c;现在整合了&#xff0c;这些都要交给spring来管理了&#xff0c;来看…

python中缩进_python编程中的缩进是什么意思

Python最具特色的是用缩进来标明成块的代码。我下面以if选择结构来举例。if后面跟随条件&#xff0c;如果条件成立&#xff0c;则执行归属于if的一个代码块。 下面对比C语言来看一下if ( i > 0 ) { x 1; y 2; } 如果i > 0的话&#xff0c;我们将进行括号中所包括的两个…

返回后的数据处理_【掘金使用技巧2】掘金返回数据中时间的处理方法

掘金输出的时间数据处理方法掘金在为使用者提供数据时&#xff0c;有一类数据处理起来有些麻烦&#xff0c;这类数据就是时间数据。它们长这样&#xff1a;或者这样&#xff1a;查看一下它们的类型&#xff0c;发现有datetime,datetime64,Timestamp等等。这么多各种各样的类型&…

springboot jwt token前后端分离_为什么要 前后端分离 ?

作 者&#xff1a;互扯程序来 源&#xff1a;互扯程序广而告之&#xff1a;由于此订阅号换了个皮肤&#xff0c;系统自动取消了读者的公众号置顶。导致用户接受文章不及时。您可以打开订阅号&#xff0c;选择置顶(星标)公众号&#xff0c;重磅干货&#xff0c;第一时间送达&…

分计算iv值_一文读懂评分卡的IV、KS、AUC、GINI指标

前言&#xff1a;当一张评分卡构建完成时&#xff0c;筛选出一组特征生成了分数&#xff0c;我们会想要知道这个分数是否靠谱&#xff0c;即是否可以依赖这个分数将好坏客户区分开来&#xff0c;这个时候就需要评判评分卡有效性的指标。测量评分卡好坏区分能力的指标有许多&…

linux 查找文件夹_用python打造一个基于socket的文件(夹)传输系统

这段时间在学习python&#xff0c;接触到了网络编程中的socket这块&#xff0c;加上自己在用的Linux服务器都是原生支持python的&#xff0c;于是乎有了个做文件传输功能程序的想法。毕竟python语言中&#xff0c;有下载功能的框架一抓一大把&#xff0c;但是主机与主机间快速搭…

mysql gtid 备份恢复_MySQL基于gtid特性与xtrabackup的数据恢复

一、gtid特性介绍&#xff1a;GTID(global transaction identifier)是MySQL 5.6的新特性&#xff0c;可以唯一的标识一个事务&#xff0c;由UUIDTID组成&#xff1a;UUID是MySQL实例的唯一标识TID是该实例上已提交的事务的数量在主从复制中&#xff0c;GTID代替了classic的复制…