ArrayBlockingQueue

有界的阻塞队列,内部是一个数组,有边界的意思是:容量是有限的,必须进行初始化,指定它的容量大小,以先进先出的方式存储数据,最新插入的在对尾,最先移除的对象在头部。

public class ArrayBlockingQueue extends AbstractQueue
implements BlockingQueue, java.io.Serializable {
 /** 队列元素 */
 final Object[] items;

 /** 下一次读取操作的位置, poll, peek or remove */
 int takeIndex;

 /** 下一次写入操作的位置, offer, or add */
 int putIndex;

 /** 元素数量 */
 int count;
 
 /*
  * Concurrency control uses the classic two-condition algorithm
  * found in any textbook.
  * 它采用一个 ReentrantLock 和相应的两个 Condition 来实现。
  */

 /** 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) {
  this(capacity, false);
 }
 
 /** 
  * 指定容量大小与指定访问策略 
  * @param fair 指定独占锁是公平锁还是非公平锁。非公平锁的吞吐量比较高,公平锁可以保证每次都是等待最久的线程获取到锁;
  */
 public ArrayBlockingQueue(int capacity, boolean fair) {}
 
 /** 
  * 指定容量大小、指定访问策略与最初包含给定集合中的元素 
  * @param c 将此集合中的元素在构造方法期间就先添加到队列中 
  */
 public ArrayBlockingQueue(int capacity, boolean fair,
        Collection c) {}
}

  • ArrayBlockingQueue 在生产者放入数据和消费者获取数据,都是共用一个锁对象,由此也意味着两者无法真正并行运行。按照实现原理来分析, ArrayBlockingQueue 完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。然而事实上并没有如此,因为 ArrayBlockingQueue 的数据写入已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。
  • 通过构造函数得知,参数 fair 控制对象内部是否采用公平锁,默认采用非公平锁。
  • items、takeIndex、putIndex、count 等属性并没有使用 volatile 修饰,这是因为访问这些变量(通过方法获取)使用都在锁内,并不存在可见性问题,如 size() 。
  • 另外有个独占锁 lock 用来对出入对操作加锁,这导致同时只有一个线程可以访问入队出队。

Put 源码分析

/** 进行入队操作 */
public void put(E e) throws InterruptedException {
  //e为null,则抛出NullPointerException异常
  checkNotNull(e);
  //获取独占锁
  final ReentrantLock lock = this.lock;
  /**
   * lockInterruptibly()
   * 获取锁定,除非当前线程为interrupted
   * 如果锁没有被另一个线程占用并且立即返回,则将锁定计数设置为1。
   * 如果当前线程已经保存此锁,则保持计数将递增1,该方法立即返回。
   * 如果锁被另一个线程保持,则当前线程将被禁用以进行线程调度,并且处于休眠状态
   * 
   */
  lock.lockInterruptibly();
  try {
   //空队列
   while (count == items.length)
    //进行条件等待处理
    notFull.await();
   //入队操作
   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();
}

这里由于在操作共享变量前加了锁,所以不存在内存不可见问题,加锁后获取的共享变量都是从主内存中获取的,而不是在CPU缓存或者寄存器里面的值,释放锁后修改的共享变量值会刷新到主内存。

另外这个队列使用循环数组实现,所以在计算下一个元素存放下标时候有些特殊。另外 insert 后调用 notEmpty.signal() ;是为了激活调用 notEmpty.await(); 阻塞后放入 notEmpty 条件队列的线程。

Take 源码分析

public E take() throws InterruptedException {
  final ReentrantLock lock = this.lock;
  lock.lockInterruptibly();
  try {
   while (count == 0)
    notEmpty.await();
   return dequeue();
  } finally {
   lock.unlock();
  }
 }
 private E dequeue() {
  // assert lock.getHoldCount() == 1;
  // assert items[takeIndex] != null;
  final Object[] items = this.items;
  @SuppressWarnings("unchecked")
  E x = (E) items[takeIndex];
  items[takeIndex] = null;
  if (++takeIndex == items.length)
   takeIndex = 0;
  count--;
  //这里有些特殊
  if (itrs != null)
   //保持队列中的元素和迭代器的元素一致
   itrs.elementDequeued();
  notFull.signal();
  return x;
}

Take 操作和 Put 操作很类似

//该类的迭代器,所有的迭代器共享数据,队列改变会影响所有的迭代器

transient Itrs itrs = null; //其存放了目前所创建的所有迭代器。

/**
* 迭代器和它们的队列之间的共享数据,允许队列元素被删除时更新迭代器的修改。
*/
class Itrs {
  void elementDequeued() {
   // assert lock.getHoldCount() == 1;
   if (count == 0)
    //队列中数量为0的时候,队列就是空的,会将所有迭代器进行清理并移除
    queueIsEmpty();
   //takeIndex的下标是0,意味着队列从尾中取完了,又回到头部获取
   else if (takeIndex == 0)
    takeIndexWrapped();
  }
  
  /**
   * 当队列为空的时候做的事情
   * 1. 通知所有迭代器队列已经为空
   * 2. 清空所有的弱引用,并且将迭代器置空
   */
  void queueIsEmpty() {}
  
  /**
   * 将takeIndex包装成0
   * 并且通知所有的迭代器,并且删除已经过期的任何对象(个人理解是置空对象)
   * 也直接的说就是在Blocking队列进行出队的时候,进行迭代器中的数据同步,保持队列中的元素和迭代器的元素是一致的。
   */
  void takeIndexWrapped() {}
}

Itrs迭代器创建的时机

//从这里知道,在ArrayBlockingQueue对象中调用此方法,才会生成这个对象
//那么就可以理解为,只要并未调用此方法,则ArrayBlockingQueue对象中的Itrs对象则为空
public Iterator iterator() {
  return new Itr();
 }
 
 private class Itr implements Iterator {
  Itr() {
   //这里就是生产它的地方
   //count等于0的时候,创建的这个迭代器是个无用的迭代器,可以直接移除,进入detach模式。
   //否则就把当前队列的读取位置给迭代器当做下一个元素,cursor存储下个元素的位置。
   if (count == 0) {
    // assert itrs == null;
    cursor = NONE;
    nextIndex = NONE;
    prevTakeIndex = DETACHED;
   } else {
    final int takeIndex = ArrayBlockingQueue.this.takeIndex;
    prevTakeIndex = takeIndex;
    nextItem = itemAt(nextIndex = takeIndex);
    cursor = incCursor(takeIndex);
    if (itrs == null) {
     itrs = new Itrs(this);
    } else {
     itrs.register(this); // in this order
     itrs.doSomeSweeping(false);
    }
    prevCycles = itrs.cycles;
    // assert takeIndex >= 0;
    // assert prevTakeIndex == takeIndex;
    // assert nextIndex >= 0;
    // assert nextItem != null;
    }
  }
}

代码演示

package com.rumenz.task;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @className: BlockingQuqueExample
 * @description: TODO 类描述
 * @author: mac
 * @date: 2021/1/20
 **/
public class BlockingQueueExample {

 private static volatile Boolean flag=false;

 public static void main(String[] args) {

 

  BlockingQueue blockingQueue=new ArrayBlockingQueue(1024);
  ExecutorService executorService = Executors.newFixedThreadPool(2);

  executorService.execute(()->{
    try{
     blockingQueue.put(1);
     Thread.sleep(2000);
     blockingQueue.put(3);
     flag=true;
    }catch (Exception e){
     e.printStackTrace();
    }
  });

  executorService.execute(()->{
   try {

    while (!flag){
     Integer i = (Integer) blockingQueue.take();
     System.out.println(i);
    }

   }catch (Exception e){
    e.printStackTrace();
   }

  });

  executorService.shutdown();
 }
}

LinkedBlockingQueue

基于链表的阻塞队列,通 ArrayBlockingQueue 类似,其内部也维护这一个数据缓冲队列(该队列由一个链表构成),当生产者往队列放入一个数据时,队列会从生产者手上获取数据,并缓存在队列的内部,而生产者立即返回,只有当队列缓冲区到达最大值容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞队列,直到消费者从队列中消费掉一份数据,生产者会被唤醒,反之对于消费者这端的处理也基于同样的原理。

LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行的操作队列中的数据,以调高整个队列的并发能力。

如果构造一个 LinkedBlockingQueue 对象,而没有指定容量大小, LinkedBlockingQueue 会默认一个类似无限大小的容量 Integer.MAX_VALUE ,这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已经被消耗殆尽了。

LinkedBlockingQueue 是一个使用链表完成队列操作的阻塞队列。链表是单向链表,而不是双向链表。

public class LinkedBlockingQueue extends AbstractQueue
implements BlockingQueue, java.io.Serializable {
 //队列的容量,指定大小或为默认值Integer.MAX_VALUE
 private final int capacity;
 
 //元素的数量
 private final AtomicInteger count = new AtomicInteger();
 
 //队列头节点,始终满足head.item==null
 transient Node head;
 
 //队列的尾节点,始终满足last.next==null
 private transient Node last;
 
 /** Lock held by take, poll, etc */
 //出队的锁:take, poll, peek 等读操作的方法需要获取到这个锁
 private final ReentrantLock takeLock = new ReentrantLock();

 /** Wait queue for waiting takes */
 //当队列为空时,保存执行出队的线程:如果读操作的时候队列是空的,那么等待 notEmpty 条件
 private final Condition notEmpty = takeLock.newCondition();

 /** Lock held by put, offer, etc */
 //入队的锁:put, offer 等写操作的方法需要获取到这个锁
 private final ReentrantLock putLock = new ReentrantLock();

 /** Wait queue for waiting puts */
 //当队列满时,保存执行入队的线程:如果写操作的时候队列是满的,那么等待 notFull 条件
 private final Condition notFull = putLock.newCondition();
 
 //传说中的无界队列
 public LinkedBlockingQueue() {}
 //传说中的有界队列
 public LinkedBlockingQueue(int capacity) {
  if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node(null);
 }
 //传说中的无界队列
 public LinkedBlockingQueue(Collection c){}
 
 /**
  * 链表节点类
  */
 static class Node {
  E item;

  /**
   * One of:
   * - 真正的继任者节点
   * - 这个节点,意味着继任者是head.next
   * - 空,意味着没有后继者(这是最后一个节点)
   */
  Node next;

  Node(E x) { item = x; }
 }
}

通过其构造函数,得知其可以当做无界队列也可以当做有界队列来使用。
这里用了两把锁分别是 takeLock 和 putLock ,而 Condition 分别是 notEmpty 和 notFull ,它们是这样搭配的。

takeLock
putLock

从上面的构造函数中可以看到,这里会初始化一个空的头结点,那么第一个元素入队的时候,队列中就会有两个元素。读取元素时,也是获取头结点后面的一个元素。count的计数值不包含这个头结点。

Put源码分析

public class LinkedBlockingQueue extends AbstractQueue
  implements BlockingQueue, java.io.Serializable { 
 /**
  * 将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用。
  */
 public void put(E e) throws InterruptedException {
  if (e == null) throw new NullPointerException();
  // 如果你纠结这里为什么是 -1,可以看看 offer 方法。这就是个标识成功、失败的标志而已。
  int c = -1;
  //包装成node节点
  Node node = new Node(e);
  final ReentrantLock putLock = this.putLock;
  final AtomicInteger count = this.count;
  //获取锁定
  putLock.lockInterruptibly();
  try {
   /** 如果队列满,等待 notFull 的条件满足。 */
   while (count.get() == capacity) {
    notFull.await();
   }
   //入队
   enqueue(node);
   //原子性自增
   c = count.getAndIncrement();
   // 如果这个元素入队后,还有至少一个槽可以使用,调用 notFull.signal() 唤醒等待线程。
   // 哪些线程会等待在 notFull 这个 Condition 上呢?
   if (c + 1 < capacity) notFull.signal(); } finally { //解锁 putLock.unlock(); } // 如果 c == 0,那么代表队列在这个元素入队前是空的(不包括head空节点), // 那么所有的读线程都在等待 notEmpty 这个条件,等待唤醒,这里做一次唤醒操作 if (c == 0) signalNotEmpty(); } /** 链接节点在队列末尾 */ private void enqueue(Node node) {
  // assert putLock.isHeldByCurrentThread();
  // assert last.next == null;
  // 入队的代码非常简单,就是将 last 属性指向这个新元素,并且让原队尾的 next 指向这个元素
  //last.next = node;
  //last = node;
  // 这里入队没有并发问题,因为只有获取到 putLock 独占锁以后,才可以进行此操作
  last = last.next = node;
 }
 
 /**
  * 等待PUT信号
  * 仅在 take/poll 中调用
  * 也就是说:元素入队后,如果需要,则会调用这个方法唤醒读线程来读
  */
 private void signalNotFull() {
  final ReentrantLock putLock = this.putLock;
  putLock.lock();
  try {
   notFull.signal();//唤醒
  } finally {
   putLock.unlock();
  }
 }
}

Take源码分析

public class LinkedBlockingQueue extends AbstractQueue
  implements BlockingQueue, java.io.Serializable { 
 public E take() throws InterruptedException {
  E x;
  int c = -1;
  final AtomicInteger count = this.count;
  final ReentrantLock takeLock = this.takeLock;
  //首先,需要获取到 takeLock 才能进行出队操作
  takeLock.lockInterruptibly();
  try {
   // 如果队列为空,等待 notEmpty 这个条件满足再继续执行
   while (count.get() == 0) {
    notEmpty.await();
   }
   //// 出队
   x = dequeue();
   //count 进行原子减 1
   c = count.getAndDecrement();
   // 如果这次出队后,队列中至少还有一个元素,那么调用 notEmpty.signal() 唤醒其他的读线程
   if (c > 1)
    notEmpty.signal();
  } finally {
   takeLock.unlock();
  }
  if (c == capacity)
   signalNotFull();
  return x;
 }
 
 /**
  * 出队
  */
 private E dequeue() {
  // assert takeLock.isHeldByCurrentThread();
  // assert head.item == null;
  Node h = head;
  Node first = h.next;
  h.next = h; // help GC
  head = first;
  E x = first.item;
  first.item = null;
  return x;
 }
 
 /**
  * Signals a waiting put. Called only from take/poll.
  */
 private void signalNotFull() {
  final ReentrantLock putLock = this.putLock;
  putLock.lock();
  try {
   notFull.signal();
  } finally {
   putLock.unlock();
  }
 }
}

与 ArrayBlockingQueue 对比

ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。

LinkedBlockingQueue 实现一个线程添加文件对象,四个线程读取文件对象

package concurrent;
import java.io.File;
import java.io.FileFilter;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class TestBlockingQueue {
 static long randomTime() {
 return (long) (Math.random() * 1000);
 }

 public static void main(String[] args) {
 // 能容纳100个文件
 final BlockingQueue queue = new LinkedBlockingQueue(100);
 // 线程池
 final ExecutorService exec = Executors.newFixedThreadPool(5);
 final File root = new File("F:\\JavaLib");
 // 完成标志
 final File exitFile = new File("");
 // 读个数
 final AtomicInteger rc = new AtomicInteger();
 // 写个数
 final AtomicInteger wc = new AtomicInteger();
 // 读线程
 Runnable read = new Runnable() {
  public void run() {
  scanFile(root);
  scanFile(exitFile);
  }

  public void scanFile(File file) {
  if (file.isDirectory()) {
   File[] files = file.listFiles(new FileFilter() {
   public boolean accept(File pathname) {
    return pathname.isDirectory()
     || pathname.getPath().endsWith(".java");
   }
   });
   for (File one : files)
   scanFile(one);
  } else {
   try {
   int index = rc.incrementAndGet();
   System.out.println("Read0: " + index + " "
    + file.getPath());
   queue.put(file);
   } catch (InterruptedException e) {
   }
  }
  }
 };
 exec.submit(read);
 // 四个写线程
 for (int index = 0; index < 4; index++) { // write thread final int NO = index; Runnable write = new Runnable() { String threadName = "Write" + NO; public void run() { while (true) { try { Thread.sleep(randomTime()); int index = wc.incrementAndGet(); File file = queue.take(); // 队列已经无对象 if (file == exitFile) { // 再次添加"标志",以让其他线程正常退出 queue.put(exitFile); break; } System.out.println(threadName + ": " + index + " " + file.getPath()); } catch (InterruptedException e) { } } } }; exec.submit(write); } exec.shutdown(); } } 

总结

到此这篇关于Java高并发BlockingQueue重要实现类的文章就介绍到这了,更多相关Java高并发BlockingQueue实现类内容请搜索乐虎体育以前的文章或继续浏览下面的相关文章希望大家以后多多支持乐虎体育!

Java高并发BlockingQueue重要的实现类详解的更多相关文章

  1. Java 给Word不同页面设置不同背景

    Word文档中,可直接通过【设计】-【页面颜色】页面颜色,通过Java代码可参考如下设置方法:1. 设置单一颜色背景doc.getBackground().setType(BackgroundType.Color);doc.getBackground().setColor(Color.PINK);2......

  2. java自定义注解验证手机格式的实现示例

    1、@Valid与@Validated的区别1.1 基本区别@Valid:Hibernate validation校验机制@Validated:Spring Validator校验机制,这个也是最常用的@Validation只是对@Valid进行了二次封装,在使用上并没有太大区别,但在分组、注解位置......

  3. java中DelayQueue实例用法详解

    在阻塞队里中,除了对元素进行增加和删除外,我们可以把元素的删除做一个延迟的处理,即使用DelayQueue的方法。这里的删除需要一定的时间才能生效,有点类似于过期处理的理念。下面我们就DelayQueue的概念、特点进行讲解,然后在代码示例中体会DelayQueue的使用。1.概念是一个带有延迟时间......

  4. Java多线程实现简易微信发红包的方法实例

    一、首先我们先大致了解一下什么是多线程。(书上的解释)程序是一段静态的代码,它是应用软件的蓝本。进程是程序的一次动态执行过程,对应了从代码加载执行,执行到执行完毕的一个完整的过程。线程不是进程,线程是比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程形成多条执行线索,每条线索即每个线程也......

  5. Java中的基本数据类型与引用数据类型

    一、基本数据类型byte、short、int、long(整数类型)float、double(浮点数类型)char(字符型)boolean(布尔类型 )Java数据大多数存放在堆栈中。栈区:存放局部变量,对象声明的引用等。堆区:存放new关键字创建的类(包含成员变量)和数组等。堆与栈的优缺点栈的优点:......

  6. java DelayQueue的原理浅析

    在对DelayQueue延迟功能的使用上,很多人不能后完全理解延迟的一些功能使用,这里我们深入来挖掘一下DelayQueue的原理。下面将从构造方法、接口、继承体系三个方面进行分析,需要注意的是,相较于其它的阻塞队列,DelayQueue因为延迟的功能多了接口的使用,一起来看具体内容。1.构造方法p......

  7. Java使用Sftp和Ftp实现对文件的上传和下载

    sftp和ftp两种方式区别,还不清楚的,请自行百度查询,此处不多赘述。完整代码地址在结尾!!第一步,导入maven依赖<!-- FTP依赖包 --><dependency><groupId>commons-net</groupId><artif......

  8. java中throws实例用法详解

    在程序出现异常时,会有一个抛出异常的throw出现,这里我们要跟今天所讲的throws区分开。throws的作用是声明抛出,在名称上也跟throw有所不同。下面我们就throws对策概念、语法、实例带来讲解,帮助大家找到声明抛出异常的方法,具体方法如下。1.概念如果方法声明的是Exception类型......

  9. Java Object类 和 String类 常见问答

    Java常见对象 Object类 和 String类 常见问答 6k字+总结写在最前面这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解。所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项目,专注 Java 后端面......

  10. Java并发/多线程-CAS原理分析

    目录什么是CAS并发安全问题举一个典型的例子i++如何解决?底层原理CAS需要注意的问题使用限制ABA 问题概念解决方案高竞争下的开销问题什么是CASCAS 即 compare and swap,比较并交换。CAS是一种原子操作,同时 CAS 使用乐观锁机制。J.U.C中的很多功能都是建立在 CAS......

随机推荐

  1. 从JAVA内存到垃圾回收,带你深入理解JVM

    摘要:学过Java的程序员对JVM应该并不陌生,如果你没有听过,没关系今天我带你走进JVM的世界。程序员为什么要学习JVM呢,其实不懂JVM也可以照样写出优质的代码,但是不懂JVM有可能别被面试官虐得体无完肤。§ 1.JAVA内存区域与内存溢出异常§ 1.1运行时数据区域......

  2. python实现自幂数的示例代码

    1、什么是自幂数?前文介绍过 python 实现水仙花数,其实水仙花数为自幂数的一种,即,3位自幂数。自幂数是指一个 n 位数,它的每个位上的数字的 n 次幂之和等于它本身。(例如:当n为3时,有1^3 + 5^3 + 3^3 = 153,153即是n为3时的一个自幂数)自幂数-百度百科2、自幂......

  3. Opencv+Python识别PCB板图片的步骤

    任务要求:基于模板匹配算法识别PCB板型号使用工具:Python3、OpenCV使用模板匹配算法,模板匹配是一种最原始、最基本的模式识别方法,研究某一特定对象物的图案位于图像的什么地方,进而识别对象物,模板匹配具有自身的局限性,主要表现在它只能进行平行移动,即原图像中的匹配目标不能发生旋转或大小变化......

  4. 删除pandas中产生Unnamed:0列的操作

    我们在数据处理,往往不小心,pandas会“主动”加上行和列的名称,我现在就遇到了这个问题。这个是pandas中to_csv生成的数据各种拼接之后的最终数据(默认参数,index=True,column=True)Unnamed: 0 ip Unnamed: 0.1 ... 766 767 ......

  5. Linux下Too many open files问题排查与解决

    作者:Grey原文地址:Github语雀博客园Too many open files是Linux系统中常见的错误,从字面意思上看就是说程序打开的文件数过多,不过这里的files不单是文件的意思,也包括打开的通讯链接(比如socket),正在监听的端口等等,所以有时候也可以叫做句柄(handle),这......

  6. Python 日志打印之logging.getLogger源码分析

    日志打印之logging.getLogger源码分析日志打印之logging.getLogger源码分析By:授客 QQ:1033553122 #实践环境WIN 10Python 3.6.5#函数说明logging.getLogger(name=None)getLogger函数位于logging/_......

  7. MySQL大库搭建主从的一种思路分享

    这个周忙的就像打仗一样,感觉有点被别人牵着鼻子走了,每天都是早出晚归,干不完的活儿,有时候感觉DBA这碗饭真的不好吃,要有强大的抗压能力和心理承受能力。今天下午吃饭的时候,真的感觉整个人快要垮掉了,吃完饭就依然决然的下班了,走在路上,看着下班的人群,心想这不就是正常的下班时间么,为什么我还有种早走惭......

  8. 全局负载均衡与CDN内容分发

    高并发时,一些静态资源(例如商品详情页)最好分配在用户较近的边缘服务器上,节省宽带资源,本文主要梳理了全网负载均衡中基于DNS、HTTP重定向、IP欺骗等具体实现方式,另外简单梳理了CDN内容分发,每一个大知识点都画图展示,一图胜前言。CDN简介CDN的全称是Content Delivery Net......

  9. JavaScript——深入了解this

    前言我曾以为func()其实就是window.func()function func(){console.log('this : ' + this);}func();//this : [object Window]window.func();//this : [object Window] 直到'u......

  10. 本章节我们讲述了如何通过admin.py来快速的完成页面功能的构建,并通过自定义action快速的实现了任务分解功能,并根据业务进展也逐步的完善了查看页面以内联表的方式显示作业详情。根据需求定义“任务”是一个完整的业务搬运流程,整个流程涉及到多个机构(设备)分别动作执行多个步骤,所以依据前面的模型设......