电信的网做的网站移动网打不开该找电信还是移动/12345微信公众号
在实习过程中使用阻塞队列对while + sleep
轮询机制进行了改造,提升了发送接收的效率,这里做一点点总结。
自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,BlcokingQueue继承了Queue接口,是线程安全的。阻塞功能使得生产者和消费者两端的能力得以平衡,当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。
等待唤醒机制 condition/await/singal 和阻塞队列的关系将在第四部分介绍。
比较和交换(Compare And Swap,CAS) 将在文章第五部分介绍。
用过 Golang 的会发现,这怎么这么像 channel
?之后应该会写写关于这俩的博客。
文章目录
- #1 阻塞队列的种类
- #2 常用方法
- 队列主要方法
- 阻塞队列主要方法
- #3 阻塞队列的原理 - 以 ArrayBlockingQueue 为例
- `put()` 方法:
- `take()` 方法
- 非阻塞队列ConcurrentLinkedQueue
- #4 等待唤醒机制 wait/notify/notifyAll、condition/await/singal
- 轮询
- wait/notify/notifyAll
- condition/await/singal
- #5 多线程安全 - CAS
#1 阻塞队列的种类
ArrayBlockingQueue
:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。LinkedBlockingQueue
:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE,可被看作无界队列。PriorityBlockingQueue
:以上2种队列都是先进先出队列,而PriorityBlockingQueue 会按照元素的优先级对元素进行排序,每次出队的元素都是优先级最高的元素。此阻塞队列为无界阻塞队列,即容量没有上限。DelayQueue
:基于PriorityQueue,一种延时阻塞队列,也有比较排序能力。DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。SynchronousQueue
:它的容量为0(不是1),所以没有地方来暂存元素,导致每次取数据都要先阻塞,直到有数据放入;同理,每次放数据的时候也会阻塞,直到有消费者来取。它不需要去持有元素,它做的就是直接传递。LinkedTransferQueue
:是一个由链表结构组成的无界阻塞TransferQueue队列,多了tryTransfer(可以设置等待时间)和transfer(等待有消费者后这个方法才会执行下去)方法。可以算是 LinkedBolckingQueue 和 SynchronousQueue 和合体。基于CAS无锁算法实现,性能理论高一点。
Queue接口下面还有一种非阻塞队列ConcurrentLickedQueue
,不会使线程阻塞,而是使用CAS非阻塞算法+不停重试来实现线程安全的。
#2 常用方法
队列主要方法
对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法却不能达到这样的效果。注意,非阻塞队列中的方法都没有进行同步措施。
add(E e)
:将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常;remove()
:移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常;element()
:返回队列的头节点,但是不会删除这个元素。当队列为空时同样会报NoSuchElementException的错误;offer(E e)
:将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false;poll()
:移除并获取队首元素,若成功,则返回队首元素;否则返回null;peek()
:获取队首元素,若成功,则返回队首元素;否则返回null
阻塞队列主要方法
上面列举的5个方法在阻塞队列中都存在,但是要注意这5个方法在阻塞队列中都进行了同步措施。
put(E e)
:阻塞方法,向队尾存入元素,如果队列满,则等待;take()
:阻塞方法,从队首取元素,如果队列为空,则等待;offer(E e,long timeout, TimeUnit unit)
:向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;poll(long timeout, TimeUnit unit)
:从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;
操作类型 | 抛出异常 | 阻塞线程 | 有返回值 | 超时返回 |
---|---|---|---|---|
存入 | add | put | offer | offer(E e, long timeout, TimeUnit unit) |
取出 | remove | take | poll | poll(long timeout, TimeUnit unit) |
#3 阻塞队列的原理 - 以 ArrayBlockingQueue 为例
ArrayBlockingQueue中用来存储元素的实际上是一个数组,takeIndex
和putIndex
分别表示队首元素和队尾元素的下标,count
表示队列中元素的个数。lock
是一个可重入锁,notEmpty
和notFull
是等待条件。
读操作和写操作都需要先获取到 ReentrantLock
独占锁才能进行下一步操作。进行读操作时如果队列为空,线程就会进入到读线程专属的notEmpty
的 Condition 的队列中去排队,等待写线程写入新的元素;同理队列已满,这个时候写操作的线程进入到写线程专属的notFull
队列中去排队,等待读线程将队列元素移除并腾出空间。
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {private static final long serialVersionUID = -817911632652898426L;/** 用于存放元素的数组 The queued items */private final E[] items;/** 下一次读取的位置 items index for next take, poll or remove */private int takeIndex;/** 下一次写入的位置 items index for next put, offer, or add. */private int putIndex;/** 队列中元素的数量 Number of items in the queue */private int count;/** Concurrency control uses the classic two-condition algorithm* found in any textbook.*//** Main lock guarding all access */private final ReentrantLock lock;/** Condition for waiting takes */private final Condition notEmpty;/** Condition for waiting puts */private final Condition notFull;
}
put()
方法:
在put方法中,首先用checkNotNull
方法检查插入的元素是不是null,如果不是 null 会用Reentrantlock上锁,并且使用的上锁方法是lock.lockInterruptibly()
。这个方法在获取锁的同时是可以响应中断的,这也正是我们的阻塞队列调用put方法时,在尝试获取锁但还没拿到锁的期间可以响应中断的底层原因。
紧接着在while循环中,它会检查当前队列是不是已经满了,也就是count
的长度是否等于数组长度。如果等于代表已经满了,于是我们便会进行等待,直到有空余的时候,我们才会执行下一步操作,调用enqueue
方法让元素进入队列,最后用unlock
方法解锁。
public void put(E e) throws InterruptedException {//判断添加元素e是否为空,为空会抛出异常checkNotNull(e);//获取锁final ReentrantLock lock = this.lock;//加锁lock.lockInterruptibly();try {//队列已满,线程等待while (count == items.length)notFull.await();//入队方法 signal()藏在enqueue(e)方法里了enqueue(e);} finally {//释放锁lock.unlock();}}private void enqueue(E x) {// assert lock.getHoldCount() == 1;// assert items[putIndex] == null;final Object[] items = this.items;//插入数据items[putIndex] = x;if (++putIndex == items.length)putIndex = 0;count++;//通知消费者线程,当前队列中有数据可供消费notEmpty.signal();
}
take()
方法
跟put方法实现很类似,只不过put方法等待的是notFull
信号,而take方法等待的是notEmpty
信号。
//从队列中取出数据
public E take() throws InterruptedException {//获取锁final ReentrantLock lock = this.lock;//加锁lock.lockInterruptibly();try {//当队列中无数据,删除线程阻塞等待while (count == 0)notEmpty.await();//有数据,执行队头元素出队操作return dequeue();} finally {//释放锁lock.unlock();}}
非阻塞队列ConcurrentLinkedQueue
顺便看看使用CAS非阻塞算法+不停重试来实现线程安全的这个队列。
ConcurrentLinkedQueue
是一种非阻塞的线程安全队列,与阻塞队列LinkedBlockingQueue
相对应,同样也是使用链表实现的FIFO队列,但不同的是它没有使用任何锁机制,而是用CAS来实现线程安全。
public boolean offer(E e) {// 检查e是不是null,是的话抛出NullPointerException异常。checkNotNull(e);// 创建新的节点final Node<E> newNode = new Node<E>(e);// 将“新的节点”添加到链表的末尾。for (Node<E> t = tail, p = t;;) {Node<E> q = p.next;// 情况1:q为空,p是尾节点插入if (q == null) {// CAS操作:如果“p的下一个节点为null”(即p为尾节点),则设置p的下一个节点为newNode。// 如果该CAS操作成功的话,则比较“p和t”(若p不等于t,则设置newNode为新的尾节点),然后返回true。// 如果该CAS操作失败,这意味着“其它线程对尾节点进行了修改”,则重新循环。if (p.casNext(null, newNode)) {if (p != t) // hop two nodes at a timecasTail(t, newNode); // Failure is OK.return true;}}// 情况2:p和q相等else if (p == q)p = (t != (t = tail)) ? t : head;// 情况3:其它elsep = (p != t && t != (t = tail)) ? t : q;}
}
非阻塞队列ConcurrentLinkedQueue主要流程就是在判空以后,会进入一个大循环中,p.casNext()方法,这个方法正是利用了CAS来操作的,这个死循环去配合CAS其实就是我们平时说的乐观锁的思想。
#4 等待唤醒机制 wait/notify/notifyAll、condition/await/singal
从上一部分可以发现,阻塞队列 = 等待唤醒机制 + 队列,例如 ArrayBlockingQueue 使用了 ReentrantLock + Condition 和一个列表队列实现。这里就顺便说说 Java 中的阻塞唤醒机制。此部分参考这篇好文。
轮询
生产者和消费者线程各自使用while循环,每隔片刻就去判断Queue的状态,队列为空时生产者才可插入数据,队列不为空时消费者才能取出数据,否则一律sleep等待。
轮询最简单但是也最不优雅。
- sleep 时不会释放线程拥有的锁。
- 轮询的方式太耗费CPU资源:如果线程过多,比如几百上千个线程同时在那轮询,会给CPU带来较大负担。
- 无法保证原子性:如果生产者的操作非原子性,消费者极可能获取到脏数据。
wait/notify/notifyAll
等待唤醒机制底层通过维护线程队列的方式,避免了过多线程同时自旋造成的CPU资源浪费,颇有点“用空间换时间”的味道。
当一个生产者线程无法插入数据时,就让它在队列里休眠(阻塞),此时生产者线程会释放CPU资源,等到消费者抢到CPU执行权并取出数据后,再由消费者唤醒生产者继续生产。
- 用
synchronized
保证原子性 wait
和notify
实现等待唤醒
// 线程consumer
synchronize(obj){ obj.wait();//没东西了,等待
}
// 线程producer
synchronize(obj){ obj.notify();//有东西了,唤醒
}
在synchronized机制下,所有等待的线程都在同一个队列里,而notify又恰巧是随机唤醒线程(也就是说,有可能生产者唤醒生产者)。
一般建议是改用notifyAll
,把所有线程都唤醒,然后大家一起参与执行权的竞争。
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
condition/await/singal
虽然 wait/notifyAll
能解决“己方唤醒己方,最终导致全部线程阻塞”的乌龙事件,但唤醒全部线程又不够精确,会造成无谓的线程竞争(实际只需要唤醒敌方线程即可)。
作为改进版,可以使用 ReentrantLock 的 Condition
替代 synchronized 的 wait/notify
// 显式锁(相对地,synchronized锁被称为隐式锁)
private final ReentrantLock lock = new ReentrantLock();
private final Condition producerCondition = lock.newCondition();
private final Condition consumerCondition = lock.newCondition();//生产
lock.lock();
producerCondition.await();
lock.unlock(); //消费
lock.lock();
consumerCondition.signal();
lock.unlock();
你可以认为lock.newCondition()
创建了一个队列,调用producerCondition.await()
会把生产者线程放入生产者的等待队列中,当消费者调用producerCondition.signal()
时会唤醒从生产者的等待队列中唤醒一个生产者线程出来工作。
也就是说,ReentrantLock的Condition通过拆分线程等待队列,让线程的等待唤醒更加精确了,想唤醒哪一方就唤醒哪一方。
可以看到,上面的伪代码和前面阻塞队列的put()
方法逻辑几乎一模一样。
#5 多线程安全 - CAS
补充一下这个概念。
在计算机科学中,比较和交换(Compare And Swap,CAS) 是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。
它包含 3 个参数 CAS(V,E,N)
。V 表示要更新的变量(内存值),E 表示预期值(旧的),N 表示新值。当且仅当 V 值等 于 E 值时,才会将 V 的值设为 N,如果 V 值和 E 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS 返回当前 V 的真实值。
CAS 操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。当多个线程同时 使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理, CAS 操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。
自旋: 就是不停的判断比较,看能否将值交换
CAS的三大问题:
- ABA问题:一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
- 循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升。
- 只能保证一个共享变量的原子操作:对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。
相关文章:

【Java】阻塞队列 BlcokingQueue 原理、与等待唤醒机制condition/await/singal的关系、多线程安全总结
在实习过程中使用阻塞队列对while sleep 轮询机制进行了改造,提升了发送接收的效率,这里做一点点总结。 自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,BlcokingQueue继承了Queue接口,是线程安全…...

【水下图像增强】Enhancing Underwater Imagery using Generative Adversarial Networks
原始题目Enhancing Underwater Imagery using Generative Adversarial Networks中文名称使用 GAN 增强水下图像发表时间2018年1月11日平台ICRA 2018来源University of Minnesota, Minneapolis MN文章链接https://arxiv.org/abs/1801.04011开源代码官方:https://gith…...

Maven专题总结—详细版
第一章 为什么使用Maven 获取jar包 使用Maven之前,自行在网络中下载jar包,效率较低。如【谷歌、百度、CSDN…】使用Maven之后,统一在一个地址下载资源jar包【阿里云镜像服务器等…】 添加jar包 使用Maven之前,将jar复制到项目工程…...

华为OD机试真题Java实现【字符串加密】真题+解题思路+代码(20222023)
字符串加密 题目 给你一串未加密的字符串str, 通过对字符串的每一个字母进行改变来实现加密, 加密方式是在每一个字母str[i]偏移特定数组元素a[i]的量, 数组a前三位已经赋值:a[0]=1,a[1]=2,a[2]=4。 当i>=3时,数组元素a[i]=a[i-1]+a[i-2]+a[i-3], 例如:原文 abcde …...

「Python 基础」函数与高阶函数
文章目录1. 函数调用函数定义函数函数的参数递归函数2. 高阶函数map/reducefiltersorted3. 函数式编程返回函数匿名函数装饰器偏函数1. 函数 函数是一种重复代码的抽象方式,Python 内建支持的一种封装; 调用函数 调用一个函数,需要知道函数…...

DIV内容滚动,文字符滚动标签marquee兼容稳定不卡
marquee(文字滚动)标签 marquee简介 <marquee>标签,是成对出现的标签,首标签<marquee>和尾标签</marquee>之间的内容就是滚动内容。 <marquee>标签的属性主要有behavior、bgcolor、direction、width、height、hspace、vspace、loop、scrollamount、scr…...

SpringBoot_第五章(Web和原理分析)
目录 1:静态资源 1.1:静态资源访问 1.2:静态资源源码解析-到WebMvcAutoConfiguration 2:Rest请求绑定(设置put和delete) 2.1:代码实例 2.2:源码分析到-WebMvcAutoConfiguratio…...

4-2 Linux进程和内存概念
文章目录前言进程状态进程优先级内存模型进程内存关系前言 进程是一个其中运行着一个或多个线程的地址空间和这些线程所需要的系统资源。一般来说,Linux系统会在进程之间共享程序代码和系统函数库,所以在任何时刻内存中都只有代码的一份拷贝。 进程状态…...

【微信小程序】计算器案例
🏆今日学习目标:第二十一期——计算器案例 ✨个人主页:颜颜yan_的个人主页 ⏰预计时间:30分钟 🎉专栏系列:我的第一个微信小程序 计算器前言实现效果实现步骤wxmlwxssjs数字按钮事件处理函数计算按钮处理事…...

408 计算机基础复试笔记 —— 更新中
计算机组成原理 计算机系统概述 问题一、冯诺依曼机基本思想 存储程序:程序和数据都存储在同一个内存中,计算机可以根据指令集执行存储在内存中的程序。这使得程序具有高度灵活性和可重用性。指令流水线:将指令分成若干阶段,每…...

找出最大数-课后程序(Python程序开发案例教程-黑马程序员编著-第二章-课后作业)
实例6:找出最大数 “脑力大乱斗”休闲益智游戏的关卡中,有一个题目是找出最大数。本实例要求编写程序,实现从输入的任意三个数中找出最大数的功能。 实例分析 对于3个数比较大小,我们可以首先先对两个数的大小进行比较ÿ…...

Java——N叉树的层序遍历
题目链接 leetcode在线oj题——N叉树的层序遍历 题目描述 给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例&…...

【Kubernetes】第十八篇 - k8s 服务发现简介
一,前言 上一篇,介绍了阿里云 ECS 服务器重启后的环境修复; 本篇,介绍 k8s 服务发现; 二,服务发现简介 当 A服务依赖了 B服务,而 B服务的IP和端口未知(或相对不固定)&…...

Codeforces Round 856 (Div. 2) 最好ak的div2
最近几场的div2 E都是一个思路啊,代码大差不差的,感觉随便ak啊。 A. Prefix and Suffix Array 题意 给你前n−1n-1n−1个字符串前缀和后n−1n-1n−1个字符串后缀,判断原字符串是否是回文串 思路 相同长度的判断是否是对称的即可。 代码 B C…...

最新JVM技术: GraalVM,让你一文了解它的方方面面
1. 什么是GraalVM? GraalVM是一种开源的虚拟机平台,由Oracle公司开发。它支持多种编程语言,包括Java、JavaScript、Python、Ruby、R、C++等,旨在提高应用程序的性能和扩展性。 GraalVM通过提供即时编译器(Just-in-Time Compiler,JIT)和Ahead-of-Time(AOT)编译器来提…...

MySQL索引失效的场景
1.like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。 2.数据库表数据量过小 如果表的数据量非常小,则MySQL可能不会使用索引,因为它认为全表扫描的代价更小。 3.or语句前后没有同时使用索引 …...

Java - 对象的比较
一、问题提出 前面讲了优先级队列,优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够进行比较,为了简单起见,我们只是插入了Integer类型, 那优先级队列中能否插入自定义类型对象呢…...

[算法]选择排序
目录 1、选择排序的实现 2、例子 3、代码实现 4、时间复杂度和空间复杂度 5、选择排序的缺点——不稳定性 1、选择排序的实现 选择排序就是每一轮选择最小的元素直接交换到左侧。这种排序的最大优势,就是省去了多余的元素交换。 2、例子 原始数组和选择排序的…...

dp模型——状态机模型C++详解
状态机定义状态机顾名思义跟状态有关系,但到底有什么关系呢。在实际解决的时候,通常把状态想成节点,状态的转换想成有向边的有向图,我们来举个例子。相信大家都玩过类似枪战的游戏(没玩过的也听说过吧)&…...

1.4 条件概率与乘法公式
1.4.1 条件概率在实际问题中,除了直接考虑某事件 B 发生的概率P(B)外,有时还会碰到这样的问题,就是“在事件A 已经发生的条件下,事件B 发生的概率”。一般情况下,后概率与前一概率不同,为了区别,我们常把后者称为条件概率,记为P(B…...

VITA/PYTHON/LUPA families
Image Sensor Group Top to Bottom Portfolio in Industrial Imaging Machine Vision • Factory automation and inspection • Robotic vision • Biometrics High-End Surveillance • Aerial Surveillance • Intelligent Traffic Systems (ITS) • Mapping Medical and Sc…...

ChatGPT概述:从模型训练到基本应用的介绍
ChatGPT概述:从模型训练到基本应用的介绍 目录 本文是对ChatGPT的由来、训练过程以及实际落地场景的解释,主要内容包括如下三个方面: 1、ChatGPT是什么 2、ChatGPT的原理 3、ChatGPT的思考 4、ChatGPT的应用 ChatGPT是什么 ChatGPT可能是近…...

C语言实现扫雷【详细讲解+全部源码】
扫雷的实现1. 配置运行环境2. 扫雷游戏的初步实现2.1 建立扫雷分布模块2.2 创建名为board的二维数组并进行棋盘初始化2.3 打印棋盘3. 接下来该讨论的事情3.1 布置雷3.2 排查雷3.3 统计坐标周围有几个雷4. 完整扫雷游戏的实现4.1 game.h4.2 game.c4.3 扫雷.c1. 配置运行环境 本游…...

Vue2.0开发之——购物车案例-Goods组件封装-商品名称和图片(46)
一 概述 循环渲染Goods组件为Goods组件封装title属性为Goods组件封装pic属性 二 循环渲染Goods组件 2.1 App.vue中导入Goods组件 import Goods from /components/Goods/Goods.vue2.2 App.vue中注册Goods组件 components: {Header,Goods}2.3 循环渲染每一个商品的信息 <…...

0201基础-组件-React
1 组件和模块 1.1 模块 对外提供特定功能的js程序,一般就是一个js文件 为什么拆分模块呢?随着业务逻辑增加,代码越来越多,越来越复杂。作用:复用js,简化js,提高js运行效率 1.2 模块化 当应用…...

论文笔记 | Conducting research in marketing with quasi-experiments
这篇论文是Journal of Marketing上的论文,讲了使用准实验来进行论文研究的一些事项。外生性识别的来源、几种准实验方法的注意点还有内生性的解决。 这篇论文对于准实验或者是平常论文的展开有一个非常友善的指导功能,可以阅读~ 摘要:本文旨…...

有关Android导览(Android Navigation component)
文章目录小结有关Android导览(Android Navigation component)碰到的问题参考小结 在使用Android导览(Android Navigation component)碰到很多问题。解决了一些问题,但是“Skipped xxx frames! The application may be doing too much work on its main thread”这样…...

01 C语言计算
C语言计算 1、变量 用途:需要存放输入的数据 定义格式:数据类型 变量名(用于区分其他变量) 变量名格式:只能由字母/下划线/数字构成,首位不能是数字;且变量名不能是标识符 **变量赋值和初始…...

java单元测试简介(基于SpringBoot)
java单元测试简介(基于SpringBoot)mockitomock创建mock对象的另一种方式:Mockverifystubbing(存根)Spy(间谍)mock 静态方法mockito在springboot mock中的实战mockito 通常,在我们写单测时,会遇…...

Linux常用命令操作
文件目录操作 查看文件列表 ls #输出列表信息 ls -l #输出详细列表信息 ls -a #输出隐藏文件 ls -la #输出包含的隐藏文件及详细信息 ll # ls-l的缩写rwx分别对应读取,写入,执行权限,前面有d代表是文件夹 创建文件 touch file.txt #创建…...