JavaEE 初阶篇-深入了解多线程安全问题(出现线程不安全的原因与解决线程不安全的方法)
🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
文章目录
1.0 多线程安全问题概述
1.1 线程不安全的实际例子
2.0 出现线程不安全的原因
2.1 线程在系统中是随机调度且抢占式执行的模式
2.2 多个线程同时修改同一个变量
2.3 线程对变量的修改操作不是“原子”
2.4 内存可见性
2.5 指令重排序
3.0 解决线程不安全问题(使用锁机制)
3.1 synchronized 关键字可以作用的地方
3.1.1 同步代码块
3.1.2 同步实例方法
3.1.3 同步静态方法
3.2 join() 方法与 synchronized 关键字的区别
4.0 加锁不合理所引发的问题
4.1 对于一个加锁与一个没有加锁的两个线程随机调度执行同一个代码块或者方法的情况
4.2 嵌套相同的锁 - 可重入锁
4.3 两个线程两把锁 - 死锁
4.4 死锁的四个必要条件
4.4.1 互斥条件
4.4.2 不可剥夺条件
4.4.3 请求保持条件
4.4.4 循环等待条件
4.5 如何避免死锁?
1.0 多线程安全问题概述
多线程安全问题是指在多线程环境下,多个线程同时访问共享资源可能导致的数据不一致、数据竞争、死锁等问题。
1.1 线程不安全的实际例子
在多线程中,很容易就会出现多线程问题,从而引发多线程安全问题。
先看以下代码:
public static long count = 0;public static void main(String[] args) throws InterruptedException {Object o = new Object();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
一般来说,t1 和 t2 都对 count 这个变量进行 count++ 这个操作,那么 count 最后的输出结果按理来说应该为 10万。但是输出的结果是不一定为 10 万,而且等于 10 万的概率非常非常非常小。
运行结果:
每次的运行结果都是不一样的,很大概率都是在 5 万到 10 万之间“徘徊”。也有可能会小于 5 万。
出现了以上的结果,都是因为多线程抢占式执行随机调度,从而导致的结果。
具体分析:
在 CPU 中执行 count++ 这一操作,大致需要执行三条指令:
1)load:将内存中的数据读取加载到 CPU 的寄存器中。
2)add:在寄存器中的值 +1 操作。
3)save:把寄存器中的值写回到内存中。
现在 t1 与 t2 两个线程并发的进行 count++ ,多线程的执行是随机调度,抢占式的执行模式。有可能会出现以下几种情况:
出现可能 1 或者可能 2 这两次 count++ 的最终结果都是 2 ,因此是正确的。而对于可能 3 这种情况,虽然说,t1 与 t2 都完成了 count++ 的操作,但是,对于可能 3 这种情况,在 t2 完成 count++ 之后,count 由 0 改为 1 ,再把值写回到内存之后,但是 t1 来说,同样也是把 count 由 0 改为 1 ,写回到内存中。简单来说,t1 将 t2 线程中 count 进行了一次覆盖,重新赋值,所以 t2 这个线程的操作是无效操作。
出现的情况有无数种,不可预计的。之所以说,最后出现的结果为 10 万的概率是非常非常小的,几乎没有可能吧。
对于出现小于 5 万的情况:
按理来说,进行了三次 count++ 操作,最后的结果应该为: count == 3,但是这里最后的结果:count == 1。这就是有可能出现 count 小于 5万的可能,出现数越加越小了。
以上就是属于多线程引发的线程安全问题。
2.0 出现线程不安全的原因
2.1 线程在系统中是随机调度且抢占式执行的模式
这是导致线程不安全的“罪魁祸首,万恶之源”,不能去改变这个机制。
2.2 多个线程同时修改同一个变量
当多个线程同时修改同一个变量时,可能会导致数据竞争和结果不确定性。这种情况下,需要采取线程安全的措施来确保数据的一致性。
2.3 线程对变量的修改操作不是“原子”
count++ 这种,不是原子操作,在 cpu 执行 count++ 操作需要三条指令。
2.4 内存可见性
2.5 指令重排序
3.0 解决线程不安全问题(使用锁机制)
锁机制可以确保在任意时刻只有一个线程可以访问共享资源,从而避免数据竞争和保证数据的一致性。
可以使用 synchronized 关键字等来实现锁机制。
代码如下:
public static long count = 0;public static void main(String[] args) throws InterruptedException {Object o = new Object();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (o){count++;}}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (o){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
通过 synchronized 关键字,把 count++ 这个操作进行了加锁,对于 o 对象可以理解为一个标志,一个锁的标识,当进入 {} 那一刻,就会加上锁,执行完代码块中的代码后,退出 {} 时,会自动解锁。
现在对于 t1 与 t2 线程来说,在每一次 for 循环之后,会抢占 “加锁” 随机调度,两个线程抢占的机会是一样的,比如说 t1 抢占到了“加锁”,那么 t2 想要再次对 count++ 加锁时,先会判断,判断当前加锁的线程是哪一个线程,如果不是自己线程,那么就会阻塞等待 t1 线程。等待 t1 执行完毕之后,t1 与 t2 会继续抢占对 count++ 这个操作进行“加锁”处理,一直循环往复。
这样就保证了 CPU 在执行 3 条指令的时候,不会被其他线程“打扰到”。每一次都是如
此:
最后的运行结果:
此时多线程问题是安全的。
补充:
1)锁本质上也是操作系统提供的功能,内核提供的功能,通过 API 给应用程序 JVM 对于这样的系统 API 又进行封装。
2)锁对象的用途,有且只有一个,就是用来区分,判断两个线程是否是针对同一个对象加锁,如果是,就会出现锁竞争/锁冲突/互斥,就会引起阻塞等待;如果不是,就不会出现锁竞争,也就不会阻塞等待。
和对象的具体是什么类型,和它里面的属性、方法,对于接下来操作这个对象统统没有任何关系。所以可以将类似 o 对象简单理解为一个标识,一个工具。
3)锁涉及的核心有两个:加锁、解锁
主要的特性:互斥,一个线程获取到锁之后,另一个线程也尝试加这个锁,就会阻塞等待(锁竞争/锁冲突)。
在代码中,可以创建出多个锁,只有多个线程竞争同一把锁,才会产生互斥,针对不同的锁,则不会。
3.1 synchronized 关键字可以作用的地方
3.1.1 同步代码块
使用 synchronized 关键字修饰代码块,可以指定对象作为锁,确保在同一时刻只有一个线程可以访问该代码块。其他线程需要等待获取锁后才能执行代码块。
public static long count = 0;public static void main(String[] args) throws InterruptedException {Object o = new Object();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {//作用于代码块中synchronized (o){count++;}}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {//作用于代码块中synchronized (o){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
3.1.2 同步实例方法
使用 synchronized 关键字修饰实例方法,可以确保在同一时刻只有一个线程可以访问该实例方法。其他线程需要等待当前线程执行完毕后才能访问。
代码如下:
public class demo11 {public synchronized void add(){count++;};public long get(){return count;};public static long count = 0;public static void main(String[] args) throws InterruptedException {demo11 demo = new demo11();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {demo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {demo.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(demo.get());} }
3.1.3 同步静态方法
使用 synchronized 关键字修饰静态方法,可以确保在同一时刻只有一个线程可以访问该静态方法。其他线程需要等待当前线程执行完毕后才能访问。
代码如下:
public class demo11 {public synchronized static void add(){count++;};public static long get(){return count;};public static long count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {demo11.add();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {demo11.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(demo11.get());} }
3.2 join() 方法与 synchronized 关键字的区别
1)join() 是 Thread 类的方法,用于等待调用该方法的线程执行完成。当一个线程调用另一个线程的 join() 方法时,它会被阻塞,直到被调用的线程执行完成。
2)synchronized 关键字用于实现线程同步确保在同一时刻只有一个线程可以访问某个代码块或方法。
总的来说,join 用于线程之间的协作和等待,而 synchronized 用于实现线程之间的同步和互斥访问共享资源。
4.0 加锁不合理所引发的问题
4.1 对于一个加锁与一个没有加锁的两个线程随机调度执行同一个代码块或者方法的情况
对于这种情况来说,同样会导致多线程安全问题。因为对于一个加锁与另一个没有加锁的情况,这两个线程之间没有锁竞争或者产生互斥,所以还是会出现多线程安全问题。
代码如下:
public class demo9 {public static long count = 0;public static void main(String[] args) throws InterruptedException {Object o = new Object();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (o){count++;}}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);} }
运行结果:
以上代码中即使有一个线程加上了锁,同样也是跟没有加锁的代码本质是一样的。因此,需要对两个线程中且操作同一个代码块或者方法进行同时加锁处理,才会解决多线程的安全问题。
4.2 嵌套相同的锁 - 可重入锁
在同一个线程中,嵌套同一个锁被称为可重入锁。
代码如下:
在外层加完锁之后,在内层继续加了相同的锁。再来了解加锁的详细过程:两个线程随机调度执行,假设 t1 对该代码块加锁,而 t2 就不能加锁了,此时产生了锁竞争,t2 需要阻塞等待 t1 执行后解锁后,才能继续去“抢夺”上锁;对于 t1 来说外层加完锁之后,此时内层加锁之前需要判断当前是那个线程对当前的代码块上锁,如果是当前线程加锁了,那么内层加锁这个操作就是为无,可以继续往下执行,注意这里没有产生锁竞争;如果不是当前线程加锁了,就会阻塞等待。
所以可重入锁是安全的,运行结果:
补充:解锁是执行到外层 } 花括号结束之后,才会自动解锁,而不是执行到内层的 } 花括号解锁。所以,内层加锁其实是没有用的,正常来说,有最外面加锁就足够了,之所以要搞上述操作,就是担心不小心把代码写错从而搞出“死锁”,目的就是避免程序员粗心大意。
4.3 两个线程两把锁 - 死锁
死锁是系统中的多个线程或进程相互等待对方释放资源,从而陷入僵局无法继续执行的状态。
代码如下:
public class demo12 {public static void main(String[] args) {Object o1 = new Object();Object o2 = new Object();Thread t1 = new Thread(()->{synchronized (o1){//这里用到 sleep 方法的原因是因为,//保证 t2 线程执行完:对 o2 进行加锁操作try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o2){System.out.println("正在执行 t1 线程");}}});Thread t2 = new Thread(()->{synchronized (o2){//这里用到 sleep 方法的原因是因为,//保证 t1 线程执行完:对 o1 进行加锁操作try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o1){System.out.println("正在执行 t2 线程");}}});t1.start();t2.start();} }
此时 t1 线程对 o1 加锁了,t2 线程对 o2 加锁了,对于 t1 来说,想要继续往下执行,需要 t2 对 o2 解锁,想要 t2 对 o2 解锁对话,需要 t1 对 o1 解锁。想要 t1 解锁的话,还是需要 t2 对 o2 解锁。。。此时成了很尴尬的情况,对方要需要对方的资源,双方都相互等待对方释放资源,从而僵持住了,无法执行下去。这样就造成了一个死锁。
通过 Jconsole 可执行程序观察:
已经检测到了死锁
运行结果:
程序一直在运行中
4.4 死锁的四个必要条件
4.4.1 互斥条件
资源只能被一个线程或进程所持有,其他线程无法同时访问。
4.4.2 不可剥夺条件
线程已经获取的资源在未使用完之前不能被其他线程所抢占。
4.4.3 请求保持条件
线程可以持有一些资源并继续请求其他资源。
4.4.4 循环等待条件
每个线程都在等待其他线程所持有的资源,形成一个循环等待的情况。
4.5 如何避免死锁?
只需要破环任意一个满足死锁的必要条件即可。
1)对于互斥条件来说,不能破坏,所以不用考虑这种情况。
2)对于不可剥夺条件,破坏该条件的方法是:如果一个线程无法获取资源,可以释放已经持有的资源,避免长时间占用资源。
3)对于请求保持条件,破坏该条件的方法是:一次性获取所有需要的资源。
4)对于循环等待条件,破坏该条件的方法是:按照固定顺序来获取资源,避免形成循环等待。
相关文章:
JavaEE 初阶篇-深入了解多线程安全问题(出现线程不安全的原因与解决线程不安全的方法)
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 多线程安全问题概述 1.1 线程不安全的实际例子 2.0 出现线程不安全的原因 2.1 线程在系统中是随机调度且抢占式执行的模式 2.2 多个线程同时修改同一个变量 2.3 线…...
计算机网络⑦ —— 网络层协议
1. ARP协议 在传输⼀个 IP 数据报的时候,确定了源 IP 地址和⽬标 IP 地址后,就会通过主机路由表确定 IP 数据包下⼀跳。然⽽,⽹络层的下⼀层是数据链路层,所以我们还要知道下⼀跳的 MAC 地址。由于主机的路由表中可以找到下⼀跳的…...
正弦实时数据库(SinRTDB)的使用(7)-历史统计查询
前文已经将正弦实时数据库的使用进行了介绍,需要了解的可以先看下面的博客: 正弦实时数据库(SinRTDB)的安装 正弦实时数据库(SinRTDB)的使用(1)-使用数据发生器写入数据 正弦实时数据库(SinRTDB)的使用(2)-接入OPC DA的数据 正弦实时数据库(SinRTDB)…...
编译和链接知识点
为什么我们在vs等编译器上写出的代码通过运行就会实现相关功能呢? 解决这个问题的关键就是关于编译与链接的知识。 首先从大的分类里有两部分:编译和链接 而编译这一大的部分又分为预处理(也叫预编译),编译…...
大话设计模式之工厂模式
工厂模式(Factory Pattern)是一种创建型设计模式,它提供了一种创建对象的最佳方式,而无需指定将要创建的对象的确切类。通过使用工厂模式,我们可以将对象的创建和使用分离,从而使代码更具灵活性和可维护性。…...
Windows MySQL通过data 文件夹恢复数据
前言 在MySql数据库中,为了备份和恢复数据,通常会使用mysqldump工具来导出和导入数据。但是,如果数据库非常大,name导出和导入数据可能会需要很长时间。这时,一种更快速的备份和恢复数据的方式就是直接复制mysql的data文件夹。 什么是mysql的…...
ARP协议定义及工作原理
ARP的定义 地址解析协议(Address Resolution Protocol,ARP):ARP协议可以将IPv4地址(一种逻辑地址)转换为各种网络所需的硬件地址(一种物理地址)。换句话说,所谓的地址解析的目标就是发现逻辑地址与物理地址的映射关系。 ARP仅用于IPv4协议&a…...
express实现用户登录和注册接口
目录 1 创建数据库2 连接数据库3 集成ORM库4 创建业务逻辑5 创建路由7 测试接口总结 我们在编写后端接口的时候操作数据库是一种常见的功能需求,express本身并不提供直接操作数据库的能力,需要借助第三方库来操作数据库,本篇讲解一下软件开发…...
数字化转型,效率增长才是王道
在当今商业世界,数字化已经成为推动企业增长的强大引擎。然而,值得注意的是,数字化并非只是简单地追求规模扩张,更重要的是实现降本增效。没有效率的增长,就像是在加速自我毁灭。在数字化转型的道路上,企业…...
RHCE-2-chrony服务器
简介 重要性 由于IT系统中,准确的计时非常重要,有很多种原因需要准确计时: 在网络传输中,数据包括和日志需要准确的时间戳 各种应用程序中,如订单信息,交易信息等 都需要准确的时间戳 Linux的两个时钟 硬…...
音频RK809
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、目的二、知识准备2.1Audio框架2.1.1 DAI2.1.2 CODEC2.1.3 machine三、原理图3.1 整体原理图3.2 喇叭部分3.3 麦克风部分四、设备树4.1 sound 部分4.2 codec 部分五、驱动讲...
解决 linux 服务器 java 命令不生效问题
在Linux系统中,当你安装Java并设置了JAVA_HOME环境变量后,你可能需要使用source /etc/profile命令来使Java命令生效。这是因为/etc/profile是一个系统级的配置文件,它包含了系统的全局环境变量设置。 但是需要注意的是,source /e…...
22 多态
目录 多态的概念多态的定义及实现抽象类多态的原理单继承和多继承关系中的虚函数表继承和多态常见的面试问题 前言 需要声明的,下面的代码和解释的哦朴实vs2013x86环境,涉及指针是4bytes,如果要其他平台下,部分代码需要改动。比…...
排序算法超详细代码和知识点整理(java版)
排序 1、冒泡排序 两层循环,相邻两个进行比较,大的推到后面去,一共比较“数组长度”轮,每一轮都是从第一个元素开始比较,每一轮比较都会将一个元素固定到数组最后的一个位置。【其实就是不停的把元素往后堆&#…...
Java复习第十二天学习笔记(JDBC),附有道云笔记链接
【有道云笔记】十二 3.28 JDBC https://note.youdao.com/s/HsgmqRMw 一、JDBC简介 面向接口编程 在JDBC里面Java这个公司只是提供了一套接口Connection、Statement、ResultSet,每个数据库厂商实现了这套接口,例如MySql公司实现了:MySql驱动…...
Python从零到一构建GPT模型
只用Python和 torch框架,从零到一构建GPT模型,对大语言模型入门,了解GPT的内部网络结构,是一个很好示例。 Build_GPT_from_Scratch.ipynb...
V R虚拟现实元宇宙的前景|虚拟现实体验店加 盟合作|V R设备在线购买
VR(虚拟现实)技术作为一种新兴的技术,正在逐渐改变人们的生活和工作方式。随着技术的不断进步,人们对于元宇宙的概念也越来越感兴趣。元宇宙是一个虚拟世界,通过VR技术可以实现人们在其中进行各种活动和交互。 元宇宙的…...
大话设计模式之策略模式
策略模式是一种行为设计模式,它允许在运行时选择算法的行为。这种模式定义了一族算法,将每个算法都封装起来,并且使它们之间可以互相替换。 在策略模式中,一个类的行为或其算法可以在运行时改变。这种模式包含以下角色࿱…...
蓝桥杯23年第十四届省赛真题-三国游戏|贪心,sort函数排序
题目链接: 1.三国游戏 - 蓝桥云课 (lanqiao.cn) 蓝桥杯2023年第十四届省赛真题-三国游戏 - C语言网 (dotcpp.com) 虽然这道题不难,很容易想到,但是这个视频的思路理得很清楚: [蓝桥杯]真题讲解:三国游戏࿰…...
P15:PATH环境变量
为什么要配置环境变量 当我们打开DOS窗口,输入:javac,出现下面问题。 原因:windows操作系统在当前目录中无法找到javac命令文件。Windows操作系统是如何搜索硬盘上某一个命令? 首先从当前目录中搜索该命令如果当前目录…...
math模块篇(七)
文章目录 math.dist(p, q)math.hypot(*coordinates)math.sin(x)math.tan(x)math.degrees(x)math.radians(x)math.acosh(x)math.asinh(x)math.atanh(x) math.dist(p, q) 在Python的math模块中,并没有一个名为math.dist(p, q)的函数。可能你是想要计算两点p和q之间的…...
wordpress插件,免费的wordpress插件
WordPress作为世界上最受欢迎的内容管理系统之一,拥有庞大的插件生态系统,为用户提供了丰富的功能扩展。在内容创作和SEO优化方面,有一类特殊的插件是自动生成原创文章并自动发布到WordPress站点的工具。这些插件能够帮助用户节省时间和精力&…...
Remote Desktop Manager for Mac:远程桌面管理软件
Remote Desktop Manager for Mac,是远程桌面管理的理想之选。它集成了多种远程连接技术,无论是SSH、RDP还是VNC,都能轻松应对,让您随时随地安全访问远程服务器和工作站。 软件下载:Remote Desktop Manager for Mac下载…...
如何撰写研究论文
SEVENTYFOUR/SHUTTERSTOCK 即使对于有经验的作家来说,将数月或数年的研究浓缩到几页纸中也是一项艰巨的任务。作者需要在令人信服地解决他们的科学问题和详细地呈现他们的结果之间找到最佳平衡点,以至于丢失了关键信息。他们必须简明扼要地描述他们的方…...
数据结构
一、栈 先进后出 二、队列 先进先出 三、数组 查询快,增加修改慢 四、链表 查询慢,增加修改慢 五、二叉树 节点: 查找二叉树 二叉查找树的特点 二叉查找树,又称二叉排序树或者二叉搜索树 每一个节点上最多有两个子节点 左子树上所…...
动态规划相关题目
文章目录 1.动态规划理论基础2.斐波那契数3.爬楼梯4.使用最小花费爬楼梯5.不同路径6.不同路径 II7. 整数拆分8. 不同的二叉搜索树 1.动态规划理论基础 1.1 什么是动态规划? 动态规划,英文:Dynamic Programming,简称DP,如果某一…...
iOS - Runtime - Class-方法缓存(cache_t)
文章目录 iOS - Runtime - Class-方法缓存(cache_t)1. 散列表的存取值 iOS - Runtime - Class-方法缓存(cache_t) Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高…...
2014年认证杯SPSSPRO杯数学建模B题(第一阶段)位图的处理算法全过程文档及程序
2014年认证杯SPSSPRO杯数学建模 B题 位图的处理算法 原题再现: 图形(或图像)在计算机里主要有两种存储和表示方法。矢量图是使用点、直线或多边形等基于数学方程的几何对象来描述图形,位图则使用像素来描述图像。一般来说&#…...
【物联网项目】基于ESP8266的家庭灯光与火情智能监测系统——文末完整工程资料源码
目录 系统介绍 硬件配置 硬件连接图 系统分析与总体设计 系统硬件设计 ESP8266 WIFI开发板 人体红外传感器模块 光敏电阻传感器模块 火焰传感器模块 可燃气体传感器模块 温湿度传感器模块 OLED显示屏模块 系统软件设计 温湿度检测模块 报警模块 OLED显示模块 …...
Unity中控制帧率的思考
如何控制帧率: 在Unity中,你可以通过设置Application.targetFrameRate来限制帧率。 例如,如果你想将帧率限制为16帧, 你可以在你的代码中添加以下行: Application.targetFrameRate 16; 通常,这行代码会放在…...
商贸公司网站建设/成人短期电脑培训班学费
本资源整理了一些在学习过程中阅读过的且感觉不错的论文,涉及聚类,向量召回,对话系统,对话状态管理,机器学习,语言模型数据集,文本相似度/匹配/分类,深度学习,语音系统&a…...
文档里网站超链接怎么做/软文推广广告
jdk1.5并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,关于两者区别,java并发编程实践里面有解释 公平锁: Threads acquire a fair lock in the order in which they requested it非公平锁:a…...
免费建站的手机app/广东seo网站优化公司
转载于:https://blog.51cto.com/241998/43668...
二级网站怎么建设/学校网站建设
http://www.yiibai.com/html/testng/2013/0914296.html 本教程介绍了TestNG中执行程序的方法,这意味着该方法被称为第一和一个接着。下面是执行程序的TestNG测试API的方法的例子。 创建一个Java类文件名TestngAnnotation.java在C:\>TestNG_WORKSPACE测试注解。 i…...
网站设计排名北京/中国婚恋网站排名
R语言学习-第二天-用R绘图R语言绘图1.绘图函数1.直方图2.散点图3.柱状图、饼图4.箱线图5.星相图6.茎叶图、Q-Q图7.热力图8.向日葵散点图9.散点图集10.三维作图11.地图绘制2.R中的统计分析1.分布函数:2.统计量计算3.相关性与回归分析4.方差分析R语言绘图 参考文献&a…...
中国机械加工网站官网/星巴克seo网络推广
国际经济与贸易就业 转载于:https://blog.51cto.com/439927/88599...