阻塞队列之四:ArrayBlockingQueue
一、ArrayBlockingQueue简介
一个由循环数组支持的有界阻塞队列。它的本质是一个基于数组的BlockingQueue的实现。 它的容纳大小是固定的。此队列按 FIFO(先进先出)原则对元素进行排序。 队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。
新元素插入到队列的尾部,队列检索操作则是从队列头部开始获得元素。 这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。 一旦创建了这样的缓存区,就不能再增加其容量。
- 试图向已满队列中放入元素会导致放入操作受阻塞,直到BlockingQueue里有新的唤空间才会被醒继续操作;
- 试图从空队列中检索元素将导致类似阻塞,直到BlocingkQueue进了新货才会被唤醒。
此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。 默认情况下,不保证是这种排序。然而,通过在构造函数将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。 公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。
注意1:它是有界阻塞队列。它是数组实现的,是一个典型的“有界缓存区”。数组大小在构造函数指定,而且从此以后不可改变。
注意2:是它线程安全的,是阻塞的,具体参考BlockingQueue的“注意4”。
注意3:不接受 null 元素
注意4:公平性 (fairness)可以在构造函数中指定。
见下面是ArrayBlockingQueue的几种构造函数:
public ArrayBlockingQueue(int capacity)
public ArrayBlockingQueue(int capacity, boolean fair)
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)
fair如果为true,则按照 FIFO 顺序访问插入或移除时受阻塞线程的队列;如果为 false,则访问顺序是不确定的。
注意5:它实现了BlockingQueue接口。
注意6:此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选方法。
注意7:其容量在构造函数中指定。容量不可以自动扩展,也没提供手动扩展的接口。
注意8:在JDK5/6中,LinkedBlockingQueue和ArrayBlocingQueue等对象的poll(long timeout, TimeUnit unit)存在内存泄露
据称JDK5会在Update12里Fix,JDK6会在Update2里Fix。
二、ArrayBlockingQueue源码分析
一个基本数组的阻塞队列。可以设置列队的大小。
它的基本原理实际还是数组,只不过存、取、删时都要做队列是否满或空的判断。然后加锁访问。
2.1、ArrayBlockingQueue的lock
首先,成员变量有一个Lock和两个Condition的定义及初始化过程如下:
ArrayBlockingQueue的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制(classic two-condition algorithm)。ArrayBlockingQueue是一个带有长度的阻塞队列,初始化的时候必须要指定队列长度,且指定长度之后不允许进行修改。
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
说明:fair是“可重入的独占锁(ReentrantLock)”的类型。fair为true,表示是公平锁;fair为false,表示是非公平锁。notEmpty和notFull是锁的两个Condition条件。
Lock的作用是提供独占锁机制,来保护竞争的资源;而Condition是为了更精细的对锁进行控制,但是依赖于lock,通过某个条件对多线程进行控制。ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。
notEmpty表示"锁的非空条件"。当某线程想从队列中获取数据的时候,而此时队列中的数据为空,则该线程通过notEmpty.await()方法进行等待;当其他线程向队列中插入元素之后,就调用notEmpty.signal()方法进行唤醒之前等待的线程。
同理,notFull表示“锁满的条件“。当某个线程向队列中插入元素,而此时队列已满时,该线程等待,即阻塞通过notFull.wait()方法;其他线程从队列中取出元素之后,就唤醒该等待的线程,这个线程调用notFull.signal()方法。
ArrayBlockingQueue使用ReentrantLock来实现的添加元素原子操作,具体可以看poll和offer中的阻塞awaitNanos(nanos)是使用了Condition中的AQS中的一个方法。
2.2、入队
增加元素有多种方法add(),offer(),put(),其中add()方法调用offer()方法。所以只用关注offer()和put()方法即可,offer()和put()不同点是,offer()在队列满时,直接返回false,不会阻塞写入线程。而put()在队列满时,会一直阻塞写入线程,直到有存储空间可以存放元素。
/**
* 添加一个元素,其实super.add里面调用了offer方法
*/
public boolean add(E e) {
return super.add(e);
} /**
*加入成功返回true,否则返回false
*
*/
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();//上锁
try {
if (count == items.length) //超过数组的容量
return false;
else {
enqueue(e); //放入元素
return true;
}
} finally {
lock.unlock();
}
} /**
* 如果队列已满的话,就会等待
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//和lock()方法的区别是让它在阻塞时也可抛出异常跳出
try {
while (count == items.length)
notFull.await(); //这里就是阻塞了,要注意。如果运行到这里,那么它会释放上面的锁,一直等到notify
enqueue(e);
} finally {
lock.unlock();
}
}
enqueue()方法是最终增加元素的方法:
/**
* 元素放入队列,注意调用这个方法时都要先加锁
*
*/
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) //循环队列,计算下标
putIndex = 0;
count++;//当前拥有元素个数加1
notEmpty.signal();//有一个元素加入成功,那肯定队列不为空
}
这里由于在操作共享变量前加了锁,所以不存在内存不可见问题,加过锁后获取的共享变量都是从主内存获取的,而不是在CPU缓存或者寄存器里面的值,释放锁后修改的共享变量值会刷新会主内存中。
另外这个队列是使用循环数组实现,所以计算下一个元素存放下标时候有些特殊。另外offer后调用 notEmpty.signal();是为了激活调用notEmpty.await()阻塞后放入notEmpty条件队列中的线程。
2.3、出队
对称的取出元素的方法有:poll(),take(),remove()方法。poll()取出元素时,当队列为空时,返回null,不会阻塞取数线程;而take()方法,当队列为空时,会阻塞取数线程,直到有元素返回为止。
//实现的方法,如果当前队列为空,返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
//实现的方法,如果当前队列为空,一直阻塞
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//队列为空,阻塞方法
return dequeue();
} finally {
lock.unlock();
}
}
//带有超时时间的取元素方法,否则返回Null
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);//超时等待
}
return dequeue();//取得元素
} finally {
lock.unlock();
}
}
remove()方法:
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i])) {
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
真正出队的方法是dequeue()方法:
/**
* 元素出队,注意调用这个方法时都要先加锁
*
*/
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;//当前拥有元素个数减1
if (itrs != null)
itrs.elementDequeued();
notFull.signal();//有一个元素取出成功,那肯定队列不满
return x;
}
2.3、完整源码
package java.util.concurrent;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.lang.ref.WeakReference;
import java.util.Spliterators;
import java.util.Spliterator; public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable { private static final long serialVersionUID = -817911632652898426L; /** 真正存入数据的数组*/
final Object[] items; /** take, poll, peek or remove的下一个索引 */
int takeIndex; /** put, offer, or add的下一个索引 */
int putIndex; /**队列中元素个数*/
int count; /**可重入锁 */
final ReentrantLock lock; /** 队列不为空的条件 */
private final Condition notEmpty; /** 队列未满的条件 */
private final Condition notFull; transient Itrs itrs = null; /**
*当前元素个数-1
*/
final int dec(int i) {
return ((i == 0) ? items.length : i) - 1;
} /**
* 返回对应索引上的元素
*/
@SuppressWarnings("unchecked")
final E itemAt(int i) {
return (E) items[i];
} /**
* 非空检查
*
* @param v the element
*/
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
} /**
* 元素放入队列,注意调用这个方法时都要先加锁
*
*/
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;//当前拥有元素个数加1
notEmpty.signal();//有一个元素加入成功,那肯定队列不为空
} /**
* 元素出队,注意调用这个方法时都要先加锁
*
*/
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;//当前拥有元素个数减1
if (itrs != null)
itrs.elementDequeued();
notFull.signal();//有一个元素取出成功,那肯定队列不满
return x;
} /**
* 指定删除索引上的元素
*
*/
void removeAt(final int removeIndex) {
final Object[] items = this.items;
if (removeIndex == takeIndex) {
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();//有一个元素删除成功,那肯定队列不满
} /**
*
* 构造函数,设置队列的初始容量
*/
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
} /**
* 构造函数。capacity设置数组大小 ,fair设置是否为公平锁
* capacity and the specified access policy.
*/
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);//是否为公平锁,如果是的话,那么先到的线程先获得锁对象。
//否则,由操作系统调度由哪个线程获得锁,一般为false,性能会比较高
notEmpty = lock.newCondition();
notFull = lock.newCondition();
} /**
*构造函数,带有初始内容的队列
*/
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair); final ReentrantLock lock = this.lock;
lock.lock(); //要给数组设置内容,先上锁
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;//依次拷贝内容
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;//如果putIndex大于数组大小 ,那么从0重新开始
} finally {
lock.unlock();//最后一定要释放锁
}
} /**
* 添加一个元素,其实super.add里面调用了offer方法
*/
public boolean add(E e) {
return super.add(e);
} /**
*加入成功返回true,否则返回false
*
*/
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();//上锁
try {
if (count == items.length) //超过数组的容量
return false;
else {
enqueue(e); //放入元素
return true;
}
} finally {
lock.unlock();
}
} /**
* 如果队列已满的话,就会等待
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//和lock()方法的区别是让它在阻塞时也可抛出异常跳出
try {
while (count == items.length)
notFull.await(); //这里就是阻塞了,要注意。如果运行到这里,那么它会释放上面的锁,一直等到notify
enqueue(e);
} finally {
lock.unlock();
}
} /**
* 带有超时时间的插入方法,unit表示是按秒、分、时哪一种
*/
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException { checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);//带有超时等待的阻塞方法
}
enqueue(e);//入队
return true;
} finally {
lock.unlock();
}
} //实现的方法,如果当前队列为空,返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
//实现的方法,如果当前队列为空,一直阻塞
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//队列为空,阻塞方法
return dequeue();
} finally {
lock.unlock();
}
}
//带有超时时间的取元素方法,否则返回Null
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);//超时等待
}
return dequeue();//取得元素
} finally {
lock.unlock();
}
}
//只是看一个队列最前面的元素,取出是不删除队列中的原来元素。队列为空时返回null
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // 队列为空时返回null
} finally {
lock.unlock();
}
} /**
* 返回队列当前元素个数
*
*/
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
} /**
* 返回当前队列再放入多少个元素就满队
*/
public int remainingCapacity() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return items.length - count;
} finally {
lock.unlock();
}
} /**
* 从队列中删除一个元素的方法。删除成功返回true,否则返回false
*/
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i])) {
removeAt(i); //真正删除的方法
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);//一直不断的循环取出来做判断
}
return false;
} finally {
lock.unlock();
}
} /**
* 是否包含一个元素
*/
public boolean contains(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i]))
return true;
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
} /**
* 清空队列
*
*/
public void clear() {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
items[i] = null;
if (++i == items.length)
i = 0;
} while (i != putIndex);
takeIndex = putIndex;
count = 0;
if (itrs != null)
itrs.queueIsEmpty();
for (; k > 0 && lock.hasWaiters(notFull); k--)
notFull.signal();
}
} finally {
lock.unlock();
}
} /**
* 取出所有元素到集合
*/
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
} /**
* 取出所有元素到集合
*/
public int drainTo(Collection<? super E> c, int maxElements) {
checkNotNull(c);
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int n = Math.min(maxElements, count);
int take = takeIndex;
int i = 0;
try {
while (i < n) {
@SuppressWarnings("unchecked")
E x = (E) items[take];
c.add(x);
items[take] = null;
if (++take == items.length)
take = 0;
i++;
}
return n;
} finally {
// Restore invariants even if c.add() threw
if (i > 0) {
count -= i;
takeIndex = take;
if (itrs != null) {
if (count == 0)
itrs.queueIsEmpty();
else if (i > take)
itrs.takeIndexWrapped();
}
for (; i > 0 && lock.hasWaiters(notFull); i--)
notFull.signal();
}
}
} finally {
lock.unlock();
}
} }
2.4、size
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
2.4、peek方法
返回队列头元素但不移除该元素,队列为空,返回null
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
三、JDK或开源框架中使用
四、使用示例
4.1、生产者-消费者模型
package com.dxz.queue.array; import java.util.concurrent.ArrayBlockingQueue; public class Producer implements Runnable{ //容器
private final ArrayBlockingQueue<Bread> queue; public Producer(ArrayBlockingQueue<Bread> queue){
this.queue = queue;
} @Override
public void run() {
while(true){
produce();
}
} public void produce(){
/**
* put()方法是如果容器满了的话就会把当前线程挂起
* offer()方法是容器如果满的话就会返回false。
*/
try {
Bread bread = new Bread();
bread.setName(System.currentTimeMillis()+"");
queue.put(bread);
System.out.println("Producer:"+bread);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} package com.dxz.queue.array; import java.util.concurrent.ArrayBlockingQueue; public class Consumer implements Runnable{ //容器
private final ArrayBlockingQueue<Bread> queue; public Consumer(ArrayBlockingQueue<Bread> queue){
this.queue = queue;
} @Override
public void run() {
while(true){
consume();
}
} public void consume(){
/**
* take()方法和put()方法是对应的,从中拿一个数据,如果拿不到线程挂起
* poll()方法和offer()方法是对应的,从中拿一个数据,如果没有直接返回null
*/
try {
Bread bread = queue.take();
//Bread bread = queue.poll();
System.out.println("consumer:"+bread);
} catch (Exception e) {
e.printStackTrace();
}
}
} package com.dxz.queue.array; import java.util.concurrent.ArrayBlockingQueue; public class Client { public static void main(String[] args) {
int capacity = 10;
ArrayBlockingQueue<Bread> queue = new ArrayBlockingQueue<Bread>(capacity); new Thread(new Producer(queue)).start();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
} }
cosumer中用take()方法的其中一次结果如下:
...
Producer:Bread [name=1500516869536]
Producer:Bread [name=1500516869536]
Producer:Bread [name=1500516869536]
consumer:Bread [name=1500516869536]
consumer:Bread [name=1500516869536]
...
cosumer中用poll()方法的其中一次结果如下:
...
consumer:Bread [name=1500517024449]
consumer:null
consumer:null
consumer:null
consumer:null
Producer:Bread [name=1500517024449]
Producer:Bread [name=1500517024449]
...
阻塞队列之四:ArrayBlockingQueue的更多相关文章
- 10.并发包阻塞队列之ArrayBlockingQueue
上一节中对并发包中的非阻塞队列ConcurrentLinkedQueue的入队.出队做了一个简要的分析,本文将对并发包中的阻塞队列做一个简要分析. Java并发包中的阻塞队列一共7个,当然他们都是线程 ...
- 并发包阻塞队列之ArrayBlockingQueue
并发包阻塞队列之ArrayBlockingQueue jdk1.7.0_79 上一节中对并发包中的非阻塞队列ConcurrentLinkedQueue的入队.出队做了一个简要的分析,本文将对并发 ...
- Java并发之BlockingQueue 阻塞队列(ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlockingQueue、SynchronousQueue)
package com.thread.test.thread; import java.util.Random; import java.util.concurrent.*; /** * Create ...
- java阻塞队列之ArrayBlockingQueue
在Java的java.util.concurrent包中定义了和多线程并发相关的操作,有许多好用的工具类,今天就来看下阻塞队列.阻塞队列很好的解决了多线程中数据的安全传输问题,其中最典型的例子就是客园 ...
- 阻塞队列---ArrayBlockingQueue,LinkedBlockingQueue,DelayQueue源码分析
阻塞队列和非阻塞队列阻塞队列和非阻塞队列的区别:阻塞队列可以自己阻塞,非阻塞队列不能自己阻塞,只能使用队列wait(),notify()进行队列消息传送.而阻塞队列当队列里面没有值时,会阻塞直到有值输 ...
- Java中的阻塞队列-ArrayBlockingQueue(一)
最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...
- Java中的阻塞队列
1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...
- java并发编程学习: 阻塞队列 使用 及 实现原理
队列(Queue)与栈(Stack)是数据结构中的二种常用结构,队列的特点是先进先出(First In First Out),而Stack是先进后出(First In Last Out),说得通俗点: ...
- java并发:阻塞队列
第一节 阻塞队列 1.1 初识阻塞队列 队列以一种先进先出的方式管理数据,阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:在队列为空时,获取元素的线程会等待队列 ...
随机推荐
- 破解google翻译API全过程
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/6554340.html 前言 google的翻译不得不承认它 ...
- Xss测试
<Script>alert(“XSS attack available!”);</Script>
- resultMap的使用
1)属性 id:resultMap的唯一标识 type:Java实体类 2)子元素 id 一般对应数据库中该行的主键id,设置此项可提高MyBatis性能 result 映射到JavaBean的某个“ ...
- Java8安装配置
方法1.命令行安装 -jdk 存在多个版本,自动配置: sudo update-alternatives --config java 方法2.下载安装 下载java8的jdk http://www.o ...
- Django的CBV
CBV CBV(class base views) 就是在视图里使用类处理请求. Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就错失了(继承.封装.多态).所以Dja ...
- 白话machine learning之Loss Function
转载自:http://eletva.com/tower/?p=186 有关Loss Function(LF),只想说,终于写了 一.Loss Function 什么是Loss Function?wik ...
- object references an unsaved transient instance【异常】
[异常提示] TransientObjectException: object references an unsaved transient instance -save the transient ...
- ubuntu 安装nodejs/npm
安装发行稳定版 Ubuntu 默认仓库里带有Node.js,版本较旧,这并不是最新版,但是应该很稳定.执行如下命令: 1 2 sudo apt-getupdate sudo apt-getinst ...
- linux自学(二)之centos7镜像安装
上一篇:linux自学(一)之vmware虚拟机安装 虚拟机安装完成之后,就可以安装centos镜像了 centos官网:https://www.centos.org/ centos7镜像 由于我的电 ...
- 每天一个linux命令:【转载】mkdir命令
linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录. 1.命令格式: mkdir [选项] 目录... 2.命令 ...