Java高并发BlockingQueue重要的实现类详解

ArrayBlockingQueue

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

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, 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<? extends E> 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<E> iterator() {
  return new Itr();
 }

 private class Itr implements Iterator<E> {
  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<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
 //队列的容量,指定大小或为默认值Integer.MAX_VALUE
 private final int capacity;

 //元素的数量
 private final AtomicInteger count = new AtomicInteger();

 //队列头节点,始终满足head.item==null
 transient Node<E> head;

 //队列的尾节点,始终满足last.next==null
 private transient Node<E> 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<E>(null);
 }
 //传说中的无界队列
 public LinkedBlockingQueue(Collection<? extends E> c){}

 /**
  * 链表节点类
  */
 static class Node<E> {
  E item;

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

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

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

takeLock
putLock

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

Put源码分析

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
  implements BlockingQueue<E>, java.io.Serializable {
 /**
  * 将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用。
  */
 public void put(E e) throws InterruptedException {
  if (e == null) throw new NullPointerException();
  // 如果你纠结这里为什么是 -1,可以看看 offer 方法。这就是个标识成功、失败的标志而已。
  int c = -1;
  //包装成node节点
  Node<E> node = new Node<E>(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<E> 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<E> extends AbstractQueue<E>
  implements BlockingQueue<E>, 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<E> h = head;
  Node<E> 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<File> queue = new LinkedBlockingQueue<File>(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实现类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java常见面试题之多线程和高并发详解

    volatile 对 volatile的理解 volatile 是一种轻量级的同步机制. 保证数据可见性 不保证原子性 禁止指令重排序 JMM JMM(Java 内存模型)是一种抽象的概念,描述了一组规则或规范,定义了程序中各个变量的访问方式. JVM运行程序的实体是线程,每个线程创建时 JVM 都会为其创建一个工作内存,是线程的私有数据区域.JMM中规定所有变量都存储在主内存,主内存是共享内存.线程对变量的操作在工作内存中进行,首先将变量从主内存拷贝到工作内存,操作完成后写会主内存.不同线程间

  • java web如何解决瞬间高并发

    1.任何的高并发,请求总是会有一个顺序的 2.java的队列的数据结构是先进先出的取值顺序 3.BlockingQueue类(线程安全)(使用方法可以百度) 一般使用LinkedBlockingQueue 利用以上几点,我们可以把高并发时候的请求放入一个队列,队列的大小可以自己定义,比如队列容量为1000个数据,那么可以利用过滤器或者拦截器把当前的请求放入队列,如果队列的容量满了,其余的请求可以丢掉或者作出相应回复 具体实施: 利用生产者.消费者模型: 将队列的请求一一处理完. 上代码: /**

  • Java使用代码模拟高并发操作的示例

    在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题.在另外一种场景下,一个资源有多个副本可供同时使用,比如打印机房有多个打印机.厕所有多个坑可供同时使用,这种情况下,Java提供了另外的并发访问控制--资源的多副本的并发访问控制,今天使用的Semaphore即是其中的一种. Java通过代码模拟高并发可以以最快的方式发现我们系统中

  • Java系统的高并发解决方法详解

    一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构.性能的要求都很简单,随着互联网业务的不断丰富,网站相关的技术经过这些年的发展,已经细分到很细的方方面面,尤其对于大型网站来说,所采用的技术更是涉及面非常广,从硬件到软件.编程语言.mysql" target="_blank" title="MySQL知识库">数据库.WebServer.防火墙等各个领域

  • 使用JAVA实现高并发无锁数据库操作步骤分享

    1. 并发中如何无锁.一个很简单的思路,把并发转化成为单线程.Java的Disruptor就是一个很好的例子.如果用java的concurrentCollection类去做,原理就是启动一个线程,跑一个Queue,并发的时候,任务压入Queue,线程轮训读取这个Queue,然后一个个顺序执行. 在这个设计模式下,任何并发都会变成了单线程操作,而且速度非常快.现在的node.js, 或者比较普通的ARPG服务端都是这个设计,"大循环"架构.这样,我们原来的系统就有了2个环境:并发环境 +

  • java高并发锁的3种实现示例代码

    初级技巧 - 乐观锁 乐观锁适合这样的场景:读不会冲突,写会冲突.同时读的频率远大于写. 以下面的代码为例,悲观锁的实现: public Object get(Object key) { synchronized(map) { if(map.get(key) == null) { // set some values } return map.get(key); } } 乐观锁的实现: public Object get(Object key) { Object val = null; if((

  • Java高并发BlockingQueue重要的实现类详解

    ArrayBlockingQueue 有界的阻塞队列,内部是一个数组,有边界的意思是:容量是有限的,必须进行初始化,指定它的容量大小,以先进先出的方式存储数据,最新插入的在对尾,最先移除的对象在头部. public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { /** 队列元素 */ final Object

  • Java 高并发的三种实现案例详解

    提到锁,大家肯定想到的是sychronized关键字.是用它可以解决一切并发问题,但是,对于系统吞吐量要求更高的话,我们这提供几个小技巧.帮助大家减小锁颗粒度,提高并发能力. 初级技巧-乐观锁 乐观锁使用的场景是,读不会冲突,写会冲突.同时读的频率远大于写.  悲观锁的实现: 悲观的认为所有代码执行都会有并发问题,所以将所有代码块都用sychronized锁住 乐观锁的实现: 乐观的认为在读的时候不会产生冲突为题,在写时添加锁.所以解决的应用场景是读远大于写时的场景. 中级技巧-String.i

  • Java 高并发八:NIO和AIO详解

    IO感觉上和多线程并没有多大关系,但是NIO改变了线程在应用层面使用的方式,也解决了一些实际的困难.而AIO是异步IO和前面的系列也有点关系.在此,为了学习和记录,也写一篇文章来介绍NIO和AIO. 1. 什么是NIO NIO是New I/O的简称,与旧式的基于流的I/O方法相对,从名字看,它表示新的一套Java I/O标 准.它是在Java 1.4中被纳入到JDK中的,并具有以下特性: NIO是基于块(Block)的,它以块为基本单位处理数据 (硬盘上存储的单位也是按Block来存储,这样性能

  • Java下Struts框架中的ActionForm类详解

    ActionForm的应用 (1) .创建一个form类必须继承四个父类中的一个.比如继承ActionForm. (2) .一个form类中的每一个属性都将和页面中form 表单中的每一个元素一一对应 例如. 一个表单为: <form> <input type="text" name="username"></input> <input type="password" name="passwor

  • Java Big Number操作BigInteger及BigDecimal类详解

    目录 BigInteger类 构造函数 类方法 BigDecimal类 BigInteger类 java.math.BigInteger 类的使用场景是大整数操作.它提供类似所有Java的基本整数运算符和java.lang.Math中的所有相关的方法的操作,如+.-.*./.%.&.|.mod.>>.<<,以及min().max()等等. 只不过它操作的整数都是极其大的,为科学计算提供了很大的便利.比如下面的代码就是计算20000000000000000000 * 3000

  • Java集合之Map接口与实现类详解

    目录 初识Map Map中常用方法 HashMap LinkedHashMap TreeMap HashMap和TreeMap的比较 Hashtable 集合中元素的遍历 iterator接口中的方法 Enumeration接口中的方法 初识Map Map接口没有从Collection接口继承,Map接口用于维护“键-值”对数据,这个“键-值”对就是Map中的元素,Map提供“键(Key)”到“值(value)”的映射,一个Map中键值必须是唯一的,不能有重复的键,因为Map中的“键-值”对元素

  • C#编程高并发的几种处理方法详解

    并发(英文Concurrency),其实是一个很泛的概念,字面意思就是"同时做多件事",不过方式有所不同.在.NET的世界里面,处理高并发大致有以下几种方法: 1.异步编程 异步编程就是使用future模式(又称promise)或者回调机制来实现(Non-blocking on waiting).如果使用回调或事件来实现(容易callback hell),不仅编写这样的代码不直观,很快就容易把代码搞得一团糟. 不过在.NET 4.5 及以上框架中引入的async/await关键字(在.

  • Java高并发测试框架JCStress详解

    前言 如果要研究高并发,一般会借助高并发工具来进行测试.JCStress(Java Concurrency Stress)它是OpenJDK中的一个高并发测试工具,它可以帮助我们研究在高并发场景下JVM,类库以及硬件等状况. JCStress学起来很简单,而且官方也提供了许多高并发场景下的测试用例,只要引入一个jar包,即可运行研究. 如何使用JCStress 此演示用maven工程,首先需要引入jar包,核心包是必须要的,样例包非必须要,此是为了演示其中的例子. <dependencies>

  • java高并发情况下高效的随机数生成器

    前言 在代码中生成随机数,是一个非常常用的功能,并且JDK已经提供了一个现成的Random类来实现它,并且Random类是线程安全的. 下面是Random.next()生成一个随机整数的实现: protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend)

  • 详解Java高并发编程之AtomicReference

    目录 一.AtomicReference 基本使用 1.1.使用 synchronized 保证线程安全性 二.了解 AtomicReference 2.1.使用 AtomicReference 保证线程安全性 2.2.AtomicReference 源码解析 2.2.1.get and set 2.2.2.lazySet 方法 2.2.3.getAndSet 方法 2.2.4.compareAndSet 方法 2.2.5.weakCompareAndSet 方法 一.AtomicReferen

随机推荐