详细剖析多线程(更新中...)

文章目录

  • 前言
  • 一、认识线程
    • 1.1线程概念
    • 1.2为什么要有线程
    • 1.3线程和进程的区别(经典面试题)
  • 二、创建线程
    • 2.1继承 Thread 类,重写run
    • 2.2实现 Runnable 接口,重写run
    • 2.3继承 Thread 类,重写run,匿名内部类
    • 2.4实现 Runnable 接口,重写run,匿名内部类
    • 2.5lambda表达式【推荐写法】
    • 2.6sleep提前唤醒
  • 三、Thread的几个常见属性
  • 四、Thread类的基本使用
    • 4.1启动线程
      • start和run的区别?(经典面试题)
    • 4.2终止线程
      • 4.3等待线程
  • 五、线程的状态


前言

在当今科技发展迅速的社会中,多线程编程已经成为一种必不可少的技能。随着计算机硬件的发展,多核处理器已经成为主流,而多线程编程可以充分利用这些处理器的性能,提高程序的运行效率。因此,掌握多线程编程已经成为程序员们必须具备的技能之一。


一、认识线程

1.1线程概念

⼀个线程就是⼀个 “执⾏流”. 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 “同时” 执⾏着多份代码.

1.2为什么要有线程

  • 尽管多进程也能实现并发编程,但是线程比进程更轻量,线程的创建、调度、销毁都比进程更快。
  • "并发编程"成为刚需,大部分现代电脑使用的操作系统都支持多核CPU,这样可以更好地利用CPU资源,提高系统性能,而并发编程能更充分利用多核CPU。
  • 有些场景需要"等待IO",为了让等待IO的时间可以去做其他工作,也需要并发编程提高效率。

1.3线程和进程的区别(经典面试题)

1.进程包含线程。
2.线程是系统调度执行的基本单位:每个线程是一个独立的执行流,可以执行一些代码,并且单独的参与到CPU的调度中( 状态、上下文、优先级、记账信息…每个线程有自己的一份);进程是系统资源分配的基本单位:每个进程有自己的资源,进程中的线程共用这一份资源(主要是内存空间和文件描述符)([^1]: 文件描述符是一个用于标识已被进程打开的文件的整数。在Unix-like操作系统中,包括Linux和Mac OS等,文件描述符是对文件、管道、套接字等I/O资源的引用。每个进程都有一个文件描述符表,其中存储了该进程打开的文件的信息)。
3.进程和进程之间不会互相影响;同一个进程中的线程之间,可能会相互干扰,引起线程安全问题,例如同一个进程中的某个线程抛出异常,可能影响到其他线程,会把整个进程中的所有线程都异常终止。
4.线程并不是越多越好,适度就行,如果线程过多,调度开销可能非常明显。

二、创建线程

2.1继承 Thread 类,重写run

package thread;
//1.创建一个自己的类,继承这个Thread
//为什么这个Thread可以直接用不需要导包?因为java标准库中,有一个特殊的包java.long,默认导入的
//为什么类前面没有public?因为一个.java文件中只能有一个public类
//这个类如果没有public包级作用于,就是只能在当前包里被其他的类使用
class MyThread extends Thread {@Overridepublic void run() {// run 方法就是该线程的入口方法.System.out.println("hello world");}
}public class ThreadDemo1 {public static void main(String[] args) {// 2. 根据刚才的类, 创建出示例. (线程实例, 才是真正的线程).// MyThread t = new MyThread();Thread t = new MyThread();// 3. 调用 Thread 的 start 方法, 才会真正调用系统 api, 在系统内核中创建出线程.t.start();}
}

2.2实现 Runnable 接口,重写run

package Thread;//实现Runnable接口,重写runclass MyThread3 implements Runnable{
//Runnable可以理解为“可执行的”,通过这个接口,可以抽象表示出一段可以被其他实体来执行的代码@Overridepublic void run() {while(true){System.out.println("hello runnable");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class ThreadDemo3 {public static void main(String[] args) throws InterruptedException {/*Runnable runnable=new MyThread3();Thread t=new MyThread(runnable);*///还是需要搭配Thread类才能真正在系统中创建出线程//这种写法其实就是把线程和要执行的任务进行了解耦合了Thread t=new Thread(new MyThread3());t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

2.3继承 Thread 类,重写run,匿名内部类

package Thread;//继承Tread重写run,但是要使用匿名内部类(在一个类里面定义一个类,匿名意味着没有名字,不能重复使用)
public class ThreadDemo4 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(){//写{}意思是要定义一个类,与此同时,这个行新的类//继承自Tread,此处{}中可以定义子类的属性和方法,此处最主要的目的是重写run方法//与此同时,这个代码还创建了子例的实例//t指向的实例(对象)并非单纯的Thread,而是Thread的子类(因为他是匿名类,所以我们也不知道他叫啥)@Overridepublic void run() {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}};t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

2.4实现 Runnable 接口,重写run,匿名内部类

package Thread;
//实现Runnable,重写run,匿名内部类
public class TreadDemo5 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(new Runnable(){//实现Runnable,重写run,匿名内部类@Overridepublic void run() {while(true){System.out.println("hello runnable");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});//Thread构造方法的参数,填写Runnable的匿名内部类的实例t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

2.5lambda表达式【推荐写法】

package Thread;//常用并推荐的写法,使用lambda表达式(匿名函数/方法)
//这个写法相当于实现了Runnable重写run,lambda代替了Runnable的位置
public class ThreadDemo6 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{ //这里的()是形参列表,这里能带参数,线程入口不需要参数//()前面应该有一个函数名,此处作为匿名函数,就没有名字while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

2.6sleep提前唤醒

在这里插入图片描述
我们在写代码的时候可以发现,sleep这行代码会报错,报错信息是受查异常。
那么什么是受查异常呢?
代码会受查异常是因为在编译时,编译器要求程序必须处理可能抛出的异常,否则会报编译错误。受查异常是指在代码中明确定义了可能发生的异常,并且在方法中必须使用try-catch或者throws关键字来处理这些异常,否则会导致编译错误。
这里的异常意味着sleep(1000)过程中,可能被提前唤醒,正常情况下会休眠满一秒才能醒过来继续往下执行,也会有特殊情况提前唤醒。
另外还有非受查异常,非受查异常是指在代码中没有明确定义,或者不受编译器检查的异常,通常是由程序逻辑错误或者环境问题导致的异常,例如空指针异常、数组越界异常等。在处理非受查异常时,可以选择捕获处理,也可以直接抛出给调用方处理。非受查异常不会导致编译错误,但是如果没有处理可能会导致程序运行时异常。
在处理sleep提前唤醒可能导致的问题时,需要谨慎设计程序逻辑,确保在提前唤醒时程序的状态和数据都是正确的。同时,需要保证程序在提前唤醒后能够正确地恢复执行。

三、Thread的几个常见属性

属性获取方法
IDgetId():jvm自动分配的身份标识,保证唯一性,不同线程不会重复
名称getName():名称是各种调试⼯具⽤到
状态getState():状态表⽰线程当前所处的⼀个情况
优先级getPriority():在java中设置优先级效果不是很明显(对内核调度器的调度过程产生了一些影响)由于系统的随机调度
是否后台线程isDaemon():也可以称为是否是"后台线程",关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏
是否存活isAlive():简单的理解,为 run ⽅法是否运⾏结束了
是否被中断isInterrupted()

以下是一些代码示例:

package Thread;
//获取线程引用class MyThread4 extends Thread{//如果是继承Thread,直接使用this拿到线程实例//如果是Runnable或者lambda的方式,就不可以,因为此时的this已经不再指向Thread对象了//就只能使用Thread.currentThread()了@Overridepublic void run() {System.out.println(this.getId()+","+this.getName());}
}
public class ThreadDemo13 {public static void main(String[] args) throws InterruptedException {MyThread4 t1=new MyThread4();MyThread4 t2=new MyThread4();t1.start();t2.start();Thread.sleep(1000);System.out.println(t1.getId()+","+t1.getName());//与run方法里面执行结果是一样的System.out.println(t2.getId()+","+t2.getName());}
}
package Thread;
//获取线程引用
//Thread.currentThread()获取到当前线程的引用public class ThreadDemo14 {public static void main(String[] args) {Thread t1=new Thread(()->{Thread t=Thread.currentThread();System.out.println(t.getName());});Thread t2=new Thread(()->{Thread t=Thread.currentThread();System.out.println(t.getName());});t1.start();t2.start();}
}
package Thread;
//前台线程的运行会阻止进程结束
//后台线程的运行不会阻止进程结束
public class ThreadDemo7 {public static void main(String[] args) {Thread t=new Thread(new Runnable(){@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}},"这是我的线程");//在start之前设置线程setDaemon为true为后台线程,进程直接结束(如果之后设置来不及)t.setDaemon(true);t.start();}
}
package Thread;public class TreadDemo8 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}});System.out.println("start之前:"+t.isAlive());t.start();System.out.println("start之后:"+t.isAlive());Thread.sleep(2000);//如果把2000改成1000,此时由于多线程调度,isAlive就未知了//2s之后线程t结束System.out.println("t结束之后:"+t.isAlive());}
}
//java代码中定义的线程对象实例,虽然表示一个线程,这个对象本身的生命周期和内核中的pcb生命周期是不完全一样的
//Thread t=new Thread(),此时t对象有了,但是内核pcb还没有,isAlive就是false
//t.start(),真正在内核中创建出这个pcb,此时isAlive就是true
//当线程run执行完了,此时内核中线程就结束了,内核pcb释放,但是此时变量t可能还存在,于是isAlive也是false

四、Thread类的基本使用

4.1启动线程

之前我们已经看到了如何通过覆写 run ⽅法创建⼀个线程对象,但线程对象被创建出来并不意味着线程就开始运⾏了。调⽤ start ⽅法, 才真的在操作系统的底层创建出⼀个线程.

start和run的区别?(经典面试题)

start和run都是线程的启动方法,但是它们之间有一些区别:

  1. start方法是启动一个新的线程,当调用start方法时,会为该线程分配一个新的栈空间,并且在线程启动后会自动调用run方法来执行线程的任务。而直接调用run方法是在当前线程中执行run方法的任务。
  2. 使用start方法来启动线程能够实现并发执行,因为每次调用start方法都会创建一个新的线程。而直接调用run方法则只是在当前线程中执行任务,无法实现并发执行。

总的来说,start方法用于启动一个新的线程来执行任务,实现并发执行,需要较多的系统资源开销。而run方法则可以在当前线程中执行任务,但无法实现并发执行,不需要额外的系统资源开销。

4.2终止线程

eg.下面这个例子在执行sleep的过程中,调用interrupt,大概率sleep休眠时间还没到就被提前唤醒,当我们去掉代码中的sleep时,能发现interrupt可以让线程顺利结束的,

public class ThreadDemo11 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {//判定是否结束System.out.println("我是一个线程, 正在工作中...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程执行完毕!");});t.start();Thread.sleep(3000);// 使用一个 interrupt 方法, 来修改刚才标志位的值,设置让线程结束System.out.println("让 t 线程结束");t.interrupt();} } ```

在这里插入图片描述

提前唤醒会出现两件事–

  • 抛出InterruptedException异常,这个异常紧接着就会被catch获取到
  • 清除Thread对象的isInterrupted标志位,意思是通过interrupt方法,已经把标志位设为true了,但是sleep提前唤醒操作,就把标志位又设回false了,此时循环还是会继续执行
    那么怎么让线程结束呢? 在catch里面加上break就可以了~
    在这里插入图片描述
    线程顺利结束~
    在这里插入图片描述
    根据上诉,为什么sleep要清空标志位呢? 是为了给程序员更多的“可操作性空间”,可以在catch语句中写一些代码做一些处理——
    1)让线程立刻结束(加上break)
    2)让线程不结束,继续执行(不加break)
    3)让线程执行一些逻辑后再结束(写一些其他代码再break)

另外,在实际开发中,catch里面应该写什么样的代码?(如果程序出现异常怎样处理更合理)
①尝试自动恢复,能自动恢复的尽量自动恢复,比如出现了一个网络通信相关的异常可以在catch尝试重连网络。
②记录日志(异常信息记录到文件中)。 ③发出警报,针对比较严重的问题,包括但不限于给程序员发邮件,发短信,打电话…
④也有少数的正常的业务逻辑,会依赖到catch,比如文件操作中的方法,就要通过catch来结束循环之类…[非常规用法]

4.3等待线程

join()方法让一个线程等待另一个线程结束。线程之间的执行顺序,是无序的调度过程,我们无法指定哪个线程何时结束,有的时候又希望能够控制线程之间结束的先后顺序,此时就可以用join,join这个方法会让等待线程阻塞,一直阻塞等待到被等待的线程执行完run方法。

在这里插入图片描述
执行join的时候,看t线程是否在运行
如果t运行中,main线程会阻塞(main线程暂时不参与cpu执行)
如果t运行结束,main线程从阻塞中恢复,并且继续往下执行

package Thread;import javax.print.attribute.standard.PresentationDirection;public class ThreadDemo12 {// t 线程把计算的结果放到 result 中.private static long result = 0;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {long tmp = 0;for (long i = 1; i <= 50_0000_0000L; i++) {//实际上计算结果太大会溢出这里先忽略这个事情tmp += i;}result += tmp;});Thread t2 = new Thread(() -> {
//            try {
//                // 如果把 join 加到末尾, 这个时候, 就还是 t 和 t2 并发执行, 没啥区别
//                // 如果把 join 加到开头, 这个时候, 就是先执行 t, t2 先阻塞. 等到 t 执行完了之后, t2 继续执行. 又成了串行执行了.
//                t.join();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }long tmp = 0;for (long i = 50_0000_0001L; i <= 100_0000_0000L; i++) {tmp += i;}result += tmp;});long beg = System.currentTimeMillis();t.start();t2.start();// 主要就是不知道 t 线程要执行多久// Thread.sleep(1000);// 使用 join, 就会严格按照 t 线程执行结束来作为等待的条件.// 什么时候 t 运行结束(计算完毕), 什么时候, join 就结束等待// t 运行 1ms, join 就等待 1ms; t 运行 10s, join 就等待 10s// 确保 join 之后得到的结果, 一定是靠谱的结果.t.join();t2.join();long end = System.currentTimeMillis();// 上面加上 join 之后, 结果就一定是 t 线程执行结束的结果了.System.out.println("result = " + result);System.out.println("time = " + (end - beg) + " ms");}
}

五、线程的状态

• NEW: Thread对象创建好了,但是还没有调用start方法在系统中创建线程。
• RUNNABLE: 就绪状态,表示这个线程正在cpu上执行,或者准备就绪随时可以去cpu执行。
• TIMED_WAITING: 指定时间的阻塞,在达到一定时间之后自动解除阻塞。
• WAITING: 不带时间的阻塞(死等),必须满足一定的条件才会解除阻塞。
• TERMINATED: Thread对象仍存在,但是系统内部的线程已经执行完毕了。
• BLOCKED: 由于锁竞争引起的阻塞。

在这里插入图片描述


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

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

相关文章

Linux信号补充——信号捕捉处理

一、信号的捕捉处理 ​ 信号保存后会在合适的时间进行处理&#xff1b; 1.1信号处理时间 ​ 进程会在操作系统的调度下处理信号&#xff0c;操作系统只管发信号&#xff0c;即信号处理是由进程完成的&#xff1b; ​ 1.信号处理首先进程得检查是否有信号&#xff1b;2.进程…

赋能智能未来:AI大模型的学习之旅

随着人工智能的迅速发展&#xff0c;AI大模型已经成为技术领域的一个热点。这些模型以其强大的数据处理能力和预测精度&#xff0c;正在不断推动着科技的边界&#xff0c;并且在医疗、金融、交通等多个行业中显示出了巨大的潜力。然而&#xff0c;构建和训练一个高效的AI大模型…

浅谈如何自我实现一个消息队列服务器(2)——实现 broker server 服务器

文章目录 一、实现 broker server 服务器1.1 创建一个SpringBoot项目1.2 创建Java类 二、硬盘持久化存储 broker server 里的数据2.1 数据库存储2.1.1 浅谈SQLiteMyBatis 2.1.2 如何使用SQLite 2.2 使用DataBaseManager类封装数据库操作2.3 文件存储消息2.3.1 存储消息时&#…

宏景eHR report_org_collect_tree.jsp SQL注入漏洞复现

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合,满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR report_org_collect_tree.jsp 接口处存在SQL注入漏洞,未经过身份认证的远程攻击者可利用此漏洞执行任意SQL指令,从而…

Docker部署Alist全平台网盘神器结合内网穿透实现无公网IP访问云盘资源

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-oZuxWTWUiXLx3aQO {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

SpringBoot实战(二十七)集成WebFlux

目录 一、WebFlux1.1 定义1.2 WebFlux 与 Spring MVC 区别 二、代码实现2.1 Maven 配置2.2 暴露 RESTful API 接口的方式方式一&#xff1a;基于注解的控制器方式二&#xff1a;函数式路由器&#xff08;Functional Endpoints&#xff09; 2.3 测试Service2.4 测试ServiceImpl2…

Streamlit实战手册:从数据应用到机器学习模型部署

Streamlit实战手册&#xff1a;从数据应用到机器学习模型部署 简介Streamlit核心功能介绍Streamlit的安装创建第一个Streamlit应用界面布局与导航数据处理与展示 Streamlit的进阶应用交互式组件按钮复选框单选按钮滑块 图表与可视化使用Matplotlib绘图使用Plotly创建交互式图表…

【题目】【网络系统管理】2019年全国职业技能大赛高职组计算机网络应用赛项H卷

极安云科专注职业教育技能竞赛培训4年&#xff0c;包含信息安全管理与评估、网络系统管理、网络搭建等多个赛项及各大CTF模块培训学习服务。本团队基于赛项知识点&#xff0c;提供完整全面的系统性理论教学与技能培训&#xff0c;成立至今持续优化教学资源与讲师结构&#xff0…

Springboot 整合 Knife4j (API文档生成工具)

目录 一、Knife4j 介绍 二、Springboot 整合 Knife4j 1、pom.xml中引入依赖包 2、在application.yml 中添加 Knife4j 相关配置 3、打开 Knife4j UI界面 三、关于Knife4j框架中常用的注解 1、Api 2、ApiOperation ​3、ApiOperationSupport(order X) ​4、ApiImplici…

Leetcode 994. 腐烂的橘子

心路历程&#xff1a; 一开始以为和刚做过的岛屿问题很像&#xff0c;只不过是把岛屿问题换成BFS去做&#xff0c;然后再加上一些计数的规则。结果做完后发现只能通过一半左右的测试用例&#xff0c;发现有一个逻辑错误在于&#xff0c;当腐烂的橘子位于两端时&#xff0c;可以…

C#探索之路基础篇(2):接口Interface的概念、实现、应用范围

文章目录 1 概念2 示例代码&#xff1a;2.1 简单接口的实现2.2 简单的使用接口2.3 使用接口呈现多态性2.4 通过接口实现一个数组迭代器2.5 通过接口来实现松耦合的关系2.6 使用接口实现可扩展、便利性 3 使用范围与时机4 注意事项 不知道大家在学习的过程中&#xff0c;有没有反…

笔试总结01

1、spring原理 1、spring原理 spring的最大作用ioc/di,将类与类的依赖关系写在配置文件中&#xff0c;程序在运行时根据配置文件动态加载依赖的类&#xff0c;降低的类与类之间的藕合度。它的原理是在applicationContext.xml加入bean标记,在bean标记中通过class属性说明具体类…

vue3+threejs新手从零开发卡牌游戏(二):初始化场景

在删掉初始化中一些没用的代码后&#xff0c;在views目录下新建game文件夹&#xff0c;在里面新建一个index.vue&#xff0c;这里就当成游戏的主入口。 目录结构如下&#xff1a; 下面开始尝试创建场景&#xff1a; 一、添加一个div作为threejs的画布对象&#xff0c;之后整个…

ubuntu - 编译 linphone-sdk

业务需求需要定制sdk&#xff0c;首先声明我们需要的是在Android4.4上跑的sdk&#xff0c;因此本次编译的sdk最低支持为19&#xff08;不同版本需要的环境不一致&#xff09;&#xff0c;编译过程较容易&#xff0c;难点在于环境配置 环境准备 Ubuntu 18.04.6 android-sdk_r24.…

mysql分页查询多用GitCode平台

目录 一、在GitCode平台AI搜索结果&#xff08;这个更优&#xff09; 二、在百度搜索输入“mysql Java分页查询”的输出结果&#xff1a; 三、推荐的文章 四、GitCode的使用 1&#xff09;如搜索jdk11可以直接下载jdk11的包 2&#xff09;搜索开源项目 3&#xff09;如搜…

步进电机驱动器的接线与使用(接线详细)

今天小编就来继续学习与使用步行电机的学习&#xff0c;如果位置对你有帮助&#xff0c;评论收藏&#xff0c;点赞一下 步进电机驱动器 步进电机驱动器是一种专用于控制步进电机的电子设备&#xff0c;用于控制步进电机的转动和位置。步进电机是一种将电信号转换为机械运动的电…

Compose UI 之 Segmented buttons 分段按钮

Segmented buttons SegmentedButton 是一种分段式按钮组件,它允许用户在一组相关的选项中选择一个或几个。 上图中:① 单选的分段式按钮。② 多选的分段式按钮。 分段式按钮的几个特点: 分段式按钮是带有状态的按钮,又有单选和多选之分。 从设计上将,不论是单选或是多选…

【GPT概念-03】:人工智能中的注意力机制

说明 注意力机制生成分数&#xff08;通常使用输入函数&#xff09;&#xff0c;确定对每个数据部分的关注程度。这些分数用于创建输入的加权总和&#xff0c;该总和馈送到下一个网络层。这允许模型捕获数据中的上下文和关系&#xff0c;而传统的固定序列处理方法可能会遗漏这…

虚拟机扩展:虚拟机快照

虚拟机快照 在学习阶段我们无法避免的可能损坏Linux操作系统。如果损坏的话&#xff0c;重新安装一个Linux操作系统就会十分麻烦。 那我们就可以通过快照将当前虚拟机的状态保存下来&#xff0c;在以后系统损坏时通过快照恢复虚拟机到保存的状态。 制作并还原快照 在VMware …

SAP HCM 0008信息类型间接评估与直接评估

如果在间接评估模块输入就是间接评估&#xff08;tarif是读取下图中的数据 a代表不需要输入工资项&#xff0c;b表示需要找工资相&#xff09; 不输入就是直接评估需要客户自己输入数字 第2个情况 summe求和 &#xff08;比如在0008中输入9000与9001 那么自动求出9002工资项数…