Java 高并发六:JDK并发包2详解

1. 线程池的基本使用

1.1.为什么需要线程池

平时的业务中,如果要使用多线程,那么我们会在业务开始前创建线程,业务结束后,销毁线程。但是对于业务来说,线程的创建和销毁是与业务本身无关的,只关心线程所执行的任务。因此希望把尽可能多的cpu用在执行任务上面,而不是用在与业务无关的线程创建和销毁上面。而线程池则解决了这个问题,线程池的作用就是将线程进行复用。

1.2.JDK为我们提供了哪些支持

JDK中的相关类图如上图所示。

其中要提到的几个特别的类。

Callable类和Runable类相似,但是区别在于Callable有返回值。

ThreadPoolExecutor是线程池的一个重要实现。

而Executors是一个工厂类。

1.3.线程池的使用

1.3.1.线程池的种类

  1. new FixedThreadPool 固定数量的线程池,线程池中的线程数量是固定的,不会改变。
  2. new SingleThreadExecutor 单一线程池,线程池中只有一个线程。
  3. new CachedThreadPool 缓存线程池,线程池中的线程数量不固定,会根据需求的大小进行改变。
  4. new ScheduledThreadPool 计划任务调度的线程池,用于执行计划任务,比如每隔5分钟怎么样,
public static ExecutorService newFixedThreadPool(int nThreads) {
 return new ThreadPoolExecutor(nThreads, nThreads,
     0L, TimeUnit.MILLISECONDS,
     new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newSingleThreadExecutor() {
 return new FinalizableDelegatedExecutorService
  (new ThreadPoolExecutor(1, 1,
     0L, TimeUnit.MILLISECONDS,
     new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newCachedThreadPool() {
 return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
     60L, TimeUnit.SECONDS,
     new SynchronousQueue<Runnable>());
}

从方法上来看,显然 FixedThreadPool,SingleThreadExecutor,CachedThreadPool都是ThreadPoolExecutor的不同实例,只是参数不同。

public ThreadPoolExecutor(int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue) {
 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  Executors.defaultThreadFactory(), defaultHandler);
}

下面来简述下 ThreadPoolExecutor构造函数中参数的含义。

  1. corePoolSize 线程池中核心线程数的数目
  2. maximumPoolSize 线程池中最多能容纳多少个线程
  3. keepAliveTime 当现在线程数目大于corePoolSize时,超过keepAliveTime时间后,多出corePoolSize的那些线程将被终结。
  4. unit keepAliveTime的单位
  5. workQueue 当任务数量很大,线程池中线程无法满足时,提交的任务会被放到阻塞队列中,线程空闲下来则会不断从阻塞队列中取数据。

这样在来看上面所说的FixedThreadPool,它的线程的核心数目和最大容纳数目都是一样的,以至于在工作期间,并不会创建和销毁线程。当任务数量很大,线程池中的线程无法满足时,任务将被保存到LinkedBlockingQueue中,而LinkedBlockingQueue的大小是Integer.MAX_VALUE。这就意味着,任务不断地添加,会使内存消耗越来越大。

而CachedThreadPool则不同,它的核心线程数量是0,最大容纳数目是Integer.MAX_VALUE,它的阻塞队列是SynchronousQueue,这是一个特别的队列,它的大小是0。由于核心线程数量是0,所以必然要将任务添加到SynchronousQueue中,这个队列只有一个线程在从中添加数据,同时另一个线程在从中获取数据时,才能成功。独自往这个队列中添加数据会返回失败。当返回失败时,则线程池开始扩展线程,这就是为什么CachedThreadPool的线程数目是不固定的。当60s该线程仍未被使用时,线程则被销毁。

1.4.线程池使用的小例子

1.4.1.简单线程池

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

public class ThreadPoolDemo {
 public static class MyTask implements Runnable {
 @Override
 public void run() {
 System.out.println(System.currentTimeMillis() + "Thread ID:"
 + Thread.currentThread().getId());
 try {
 Thread.sleep(1000);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }

 public static void main(String[] args) {
 MyTask myTask = new MyTask();
 ExecutorService es = Executors.newFixedThreadPool(5);
 for (int i = 0; i < 10; i++) {
 es.submit(myTask);
 }
 }
}

由于使用的newFixedThreadPool(5),但是启动了10个线程,所以每次执行5个,并且 可以很明显的看到线程的复用,ThreadId是重复的,也就是前5个任务和后5个任务都是同一批线程去执行的。

这里用的是

es.submit(myTask);

还有一种提交方式:

es.execute(myTask);

区别在于submit会返回一个Future对象,这个将在以后介绍。

1.4.2.ScheduledThreadPool

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo {
 public static void main(String[] args) {
 ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
 //如果前面的任务还未完成,则调度不会启动。
 ses.scheduleWithFixedDelay(new Runnable() {

 @Override
 public void run() {
 try {
 Thread.sleep(1000);
 System.out.println(System.currentTimeMillis()/1000);
 } catch (Exception e) {
 // TODO: handle exception
 }

 }
 }, 0, 2, TimeUnit.SECONDS);//启动0秒后执行,然后周期2秒执行一次
 }
}

输出:

1454832514
1454832517
1454832520
1454832523
1454832526
...

由于任务执行需要1秒,任务调度必须等待前一个任务完成。也就是这里的每隔2秒的意思是,前一个任务完成后2秒再开启新的一个任务。

2. 扩展和增强线程池

2.1.回调接口

线程池中有一些回调的api来给我们提供扩展的操作。

ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS,
 new LinkedBlockingQueue<Runnable>()){

 @Override
 protected void beforeExecute(Thread t, Runnable r) {
 System.out.println("准备执行");
 }

 @Override
 protected void afterExecute(Runnable r, Throwable t) {
 System.out.println("执行完成");
 }

 @Override
 protected void terminated() {
 System.out.println("线程池退出");
 }

 };

我们可以通过实现ThreadPoolExecutor的子类去覆盖ThreadPoolExecutor的beforeExecute,afterExecute,terminated方法来实现在线程执行前后,线程池退出时的日志管理或其他操作。

2.2.拒绝策略

有时候,任务非常繁重,导致系统负载太大。在上面说过,当任务量越来越大时,任务都将放到FixedThreadPool的阻塞队列中,导致内存消耗太大,最终导致内存溢出。这样的情况是应该要避免的。因此当我们发现线程数量要超过最大线程数量时,我们应该放弃一些任务。丢弃时,我们应该把任务记下来,而不是直接丢掉。

ThreadPoolExecutor中还有另一个构造函数。

public ThreadPoolExecutor(int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler) {
 if (corePoolSize < 0 ||
  maximumPoolSize <= 0 ||
  maximumPoolSize < corePoolSize ||
  keepAliveTime < 0)
  throw new IllegalArgumentException();
 if (workQueue == null || threadFactory == null || handler == null)
  throw new NullPointerException();
 this.corePoolSize = corePoolSize;
 this.maximumPoolSize = maximumPoolSize;
 this.workQueue = workQueue;
 this.keepAliveTime = unit.toNanos(keepAliveTime);
 this.threadFactory = threadFactory;
 this.handler = handler;
 }

threadFactory我们在后面再介绍。
而handler就是拒绝策略的实现,它会告诉我们,如果任务不能执行了,该怎么做。

共有以上4种策略。

AbortPolicy:如果不能接受任务了,则抛出异常。

CallerRunsPolicy:如果不能接受任务了,则让调用的线程去完成。

DiscardOldestPolicy:如果不能接受任务了,则丢弃最老的一个任务,由一个队列来维护。

DiscardPolicy:如果不能接受任务了,则丢弃任务。

ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS,
 new LinkedBlockingQueue<Runnable>(),
 new RejectedExecutionHandler() {

 @Override
 public void rejectedExecution(Runnable r,
 ThreadPoolExecutor executor) {
 System.out.println(r.toString() + "is discard");
 }
 });

当然我们也可以自己实现RejectedExecutionHandler接口来自己定义拒绝策略。

2.3.自定义ThreadFactory

刚刚已经看到了,在ThreadPoolExecutor的构造函数中可以指定threadFactory。

线程池中的线程都是由线程工厂创建出来,我们可以自定义线程工厂。

默认的线程工厂:

static class DefaultThreadFactory implements ThreadFactory {
 private static final AtomicInteger poolNumber = new AtomicInteger(1);
 private final ThreadGroup group;
 private final AtomicInteger threadNumber = new AtomicInteger(1);
 private final String namePrefix;

 DefaultThreadFactory() {
  SecurityManager s = System.getSecurityManager();
  group = (s != null) ? s.getThreadGroup() :
     Thread.currentThread().getThreadGroup();
  namePrefix = "pool-" +
    poolNumber.getAndIncrement() +
    "-thread-";
 }

 public Thread newThread(Runnable r) {
  Thread t = new Thread(group, r,
     namePrefix + threadNumber.getAndIncrement(),
     0);
  if (t.isDaemon())
  t.setDaemon(false);
  if (t.getPriority() != Thread.NORM_PRIORITY)
  t.setPriority(Thread.NORM_PRIORITY);
  return t;
 }
 }

3. ForkJoin

3.1.思想

就是分而治之的思想。

fork/join类似MapReduce算法,两者区别是:Fork/Join 只有在必要时如任务非常大的情况下才分割成一个个小任务,而 MapReduce总是在开始执行第一步进行分割。看来,Fork/Join更适合一个JVM内线程级别,而MapReduce适合分布式系统。

4.2.使用接口

RecursiveAction:无返回值
RecursiveTask:有返回值

4.3.简单例子

import java.util.ArrayList;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class CountTask extends RecursiveTask<Long>{

 private static final int THRESHOLD = 10000;
 private long start;
 private long end;

 public CountTask(long start, long end) {
 super();
 this.start = start;
 this.end = end;
 }

 @Override
 protected Long compute() {
 long sum = 0;
 boolean canCompute = (end - start) < THRESHOLD;
 if(canCompute)
 {
 for (long i = start; i <= end; i++) {
 sum = sum + i;
 }
 }else
 {
 //分成100个小任务
 long step = (start + end)/100;
 ArrayList<CountTask> subTasks = new ArrayList<CountTask>();
 long pos = start;
 for (int i = 0; i < 100; i++) {
 long lastOne = pos + step;
 if(lastOne > end )
 {
 lastOne = end;
 }
 CountTask subTask = new CountTask(pos, lastOne);
 pos += step + 1;
 subTasks.add(subTask);
 subTask.fork();//把子任务推向线程池
 }
 for (CountTask t : subTasks) {
 sum += t.join();//等待所有子任务结束
 }
 }
 return sum;
 }

 public static void main(String[] args) {
 ForkJoinPool forkJoinPool = new ForkJoinPool();
 CountTask task = new CountTask(0, 200000L);
 ForkJoinTask<Long> result = forkJoinPool.submit(task);
 try {
 long res = result.get();
 System.out.println("sum = " + res);
 } catch (Exception e) {
 // TODO: handle exception
 e.printStackTrace();
 }
 }

}

上述例子描述了一个累加和的任务。将累加任务分成100个任务,每个任务只执行一段数字的累加和,最后join后,把每个任务计算出的和再累加起来。

4.4.实现要素

4.4.1.WorkQueue与ctl

每一个线程都会有一个工作队列

static final class WorkQueue

在工作队列中,会有一系列对线程进行管理的字段

volatile int eventCount;   // encoded inactivation count; < 0 if inactive
        int nextWait;              // encoded record of next event waiter
        int nsteals;               // number of steals
        int hint;                  // steal index hint
        short poolIndex;           // index of this queue in pool
        final short mode;          // 0: lifo, > 0: fifo, < 0: shared
        volatile int qlock;        // 1: locked, -1: terminate; else 0
        volatile int base;         // index of next slot for poll
        int top;                   // index of next slot for push
        ForkJoinTask<?>[] array;   // the elements (initially unallocated)
        final ForkJoinPool pool;   // the containing pool (may be null)
        final ForkJoinWorkerThread owner; // owning thread or null if shared
        volatile Thread parker;    // == owner during call to park; else null
        volatile ForkJoinTask<?> currentJoin;  // task being joined in awaitJoin
        ForkJoinTask<?> currentSteal; // current non-local task being executed

这里要注意的是,JDK7和JDK8在ForkJoin的实现上有了很大的差别。我们这里介绍的是JDK8中的。 在线程池中,有时不是所有的线程都在执行的,部分线程会被挂起,那些挂起的线程会被存放到一个栈中。内部通过一个链表表示。
nextWait会指向下一个等待的线程。

poolIndex线程在线程池中的下标索引。

eventCount 在初始化时,eventCount与poolIndex有关。总共32位,第一位表示是否被激活,15位表示被挂起的次数

eventCount,剩下的表示poolIndex。用一个字段来表示多个意思。

工作队列WorkQueue用ForkJoinTask<?>[] array来表示。而top,base来表示队列的两端,数据在这两者之间。

在ForkJoinPool中维护着ctl(64位long型)

volatile long ctl;

* Field ctl is a long packed with:
     * AC: Number of active running workers minus target parallelism (16 bits)
     * TC: Number of total workers minus target parallelism (16 bits)
     * ST: true if pool is terminating (1 bit)
     * EC: the wait count of top waiting thread (15 bits)
     * ID: poolIndex of top of Treiber stack of waiters (16 bits)

AC表示活跃的线程数减去并行度(大概就是CPU个数)

TC表示总的线程数减去并行度

ST表示线程池本身是否是激活的

EC表示顶端等待线程的挂起数

ID表示顶端等待线程的poolIndex

很明显ST+EC+ID就是我们刚刚所说的 eventCount 。

那么为什么明明5个变量,非要合成一个变量呢。其实用5个变量占用容量也差不多。

用一个变量代码的可读性上会差很多。

那么为什么用一个变量呢?其实这点才是最巧妙的地方,因为这5个变量是一个整体,在多线程中,如果用5个变量,那么当修改其中一个变量时,如何保证5个变量的整体性。那么用一个变量则就解决了这个问题。如果用锁解决,则会降低性能。

用一个变量则保证了数据的一致性和原子性。

在ForkJoin中队ctl的更改都是使用CAS操作,在前面系列的文章中已经介绍过,CAS是无锁的操作,性能很好。

由于CAS操作也只能针对一个变量,所以这种设计是最优的。

4.4.2.工作窃取

接下来要介绍下整个线程池的工作流程。

每个线程都会调用runWorker

final void runWorker(WorkQueue w) {
 w.growArray(); // allocate queue
 for (int r = w.hint; scan(w, r) == 0; ) {
  r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
 }
 }

scan()函数是扫描是否有任务要做。

r是一个相对随机的数字。

private final int scan(WorkQueue w, int r) {
 WorkQueue[] ws; int m;
 long c = ctl;    // for consistency check
 if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && w != null) {
  for (int j = m + m + 1, ec = w.eventCount;;) {
  WorkQueue q; int b, e; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
  if ((q = ws[(r - j) & m]) != null &&
   (b = q.base) - q.top < 0 && (a = q.array) != null) {
   long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
   if ((t = ((ForkJoinTask<?>)
    U.getObjectVolatile(a, i))) != null) {
   if (ec < 0)
    helpRelease(c, ws, w, q, b);
   else if (q.base == b &&
     U.compareAndSwapObject(a, i, t, null)) {
    U.putOrderedInt(q, QBASE, b + 1);
    if ((b + 1) - q.top < 0)
    signalWork(ws, q);
    w.runTask(t);
   }
   }
   break;
  }
  else if (--j < 0) {
   if ((ec | (e = (int)c)) < 0) // inactive or terminating
   return awaitWork(w, c, ec);
   else if (ctl == c) {  // try to inactivate and enqueue
   long nc = (long)ec | ((c - AC_UNIT) & (AC_MASK|TC_MASK));
   w.nextWait = e;
   w.eventCount = ec | INT_SIGN;
   if (!U.compareAndSwapLong(this, CTL, c, nc))
    w.eventCount = ec; // back out
   }
   break;
  }
  }
 }
 return 0;
 }

我们接下来看看scan方法,scan的一个参数是WorkQueue,上面已经说过,每个线程都会拥有一个WorkQueue,那么多个线程的WorkQueue就会保存在workQueues里面,r是一个随机数,通过r来找到某一个 WorkQueue,在WorkQueue里面有所要做的任务。

然后通过WorkQueue的base,取得base的偏移量。

b = q.base
..
long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
..

然后通过偏移量得到最后一个的任务,运行这个任务

t = ((ForkJoinTask<?>)U.getObjectVolatile(a, i))
..
w.runTask(t);
..

通过这个大概的分析理解了过程,我们发现,当前线程调用scan方法后,不会执行当前的WorkQueue中的任务,而是通过一个随机数r,来得到其他 WorkQueue的任务。这就是ForkJoinPool的主要的一个机理。
当前线程不会只着眼于自己的任务,而是优先完成其他任务。这样做来,防止了饥饿现象的发生。这样就预防了某些线程因为卡死或者其他原因而无法及时完成任务,或者某个线程的任务量很大,其他线程却没事可做。

然后来看看runTask方法

final void runTask(ForkJoinTask<?> task) {
  if ((currentSteal = task) != null) {
  ForkJoinWorkerThread thread;
  task.doExec();
  ForkJoinTask<?>[] a = array;
  int md = mode;
  ++nsteals;
  currentSteal = null;
  if (md != 0)
   pollAndExecAll();
  else if (a != null) {
   int s, m = a.length - 1;
   ForkJoinTask<?> t;
   while ((s = top - 1) - base >= 0 &&
    (t = (ForkJoinTask<?>)U.getAndSetObject
    (a, ((m & s) << ASHIFT) + ABASE, null)) != null) {
   top = s;
   t.doExec();
   }
  }
  if ((thread = owner) != null) // no need to do in finally clause
   thread.afterTopLevelExec();
  }
 }

有一个有趣的命名:currentSteal,偷得的任务,的确是刚刚解释的那样。

task.doExec();

将会完成这个任务。

完成了别人的任务以后,将会完成自己的任务。

通过得到top来获得自己任务第一个任务

while ((s = top - 1) - base >= 0 && (t = (ForkJoinTask<?>)U.getAndSetObject(a, ((m & s) << ASHIFT) + ABASE, null)) != null)
{
 top = s;
 t.doExec();
}

接下来,通过一个图来总结下刚刚线程池的流程

比如有T1,T2两个线程,T1会通过T2的base来获得T2的最后一个任务(当然实际上是通过一个随机数r来取得某个线程最后一个任务),T1也会通过自己的top来执行自己的第一个任务。反之,T2也会如此。
拿其他线程的任务都是从base开始拿的,自己拿自己的任务是从top开始拿的。这样可以减少冲突

如果没有找到其他任务

else if (--j < 0) {
   if ((ec | (e = (int)c)) < 0) // inactive or terminating
   return awaitWork(w, c, ec);
   else if (ctl == c) {  // try to inactivate and enqueue
   long nc = (long)ec | ((c - AC_UNIT) & (AC_MASK|TC_MASK));
   w.nextWait = e;
   w.eventCount = ec | INT_SIGN;
   if (!U.compareAndSwapLong(this, CTL, c, nc))
    w.eventCount = ec; // back out
   }
   break;
  }

那么首先会通过一系列运行来改变ctl的值,获得了nc,然后用CAS将新的值赋值。然后就调用awaitWork()将线程进入等待状态(调用的 前面系列文章中提到的unsafe的park方法)。
这里要说明的是改变ctl值这里,首先是将ctl中的AC-1,AC是占ctl的前16位,所以不能直接-1,而是通过AC_UNIT(0x1000000000000)来达到使ctl的前16位-1的效果。

前面说过eventCount中有保存poolIndex,通过poolIndex以及WorkQueue中的nextWait,就能遍历所有的等待线程。

(0)

相关推荐

  • 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系统的高并发解决方法详解

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

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

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

  • Java 高并发十: JDK8对并发的新支持详解

    1. LongAdder 和AtomicLong类似的使用方式,但是性能比AtomicLong更好. LongAdder与AtomicLong都是使用了原子操作来提高性能.但是LongAdder在AtomicLong的基础上进行了热点分离,热点分离类似于有锁操作中的减小锁粒度,将一个锁分离成若干个锁来提高性能.在无锁中,也可以用类似的方式来增加CAS的成功率,从而提高性能. LongAdder原理图: AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CA

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

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

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

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

  • Java 处理高并发负载类优化方法案例详解

    java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,java高负载数据) 一:高并发高负载类网站关注点之数据库 没错,首先是数据库,这是大多数应用所面临的首个SPOF.尤其是Web2.0的应用,数据库的响应是首先要解决的. 一般来说MySQL是最常用的,可能最初是一个mysql主机,当数据增加到100万以上,那么,MySQL的效能急剧下降.常用的优化措施是M-S(主-从)方式进行同步复制,将查询和操作和分别在不同的服务器上进行操作.我推荐的是M-M-Slaves

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

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

  • Java 高并发六:JDK并发包2详解

    1. 线程池的基本使用 1.1.为什么需要线程池 平时的业务中,如果要使用多线程,那么我们会在业务开始前创建线程,业务结束后,销毁线程.但是对于业务来说,线程的创建和销毁是与业务本身无关的,只关心线程所执行的任务.因此希望把尽可能多的cpu用在执行任务上面,而不是用在与业务无关的线程创建和销毁上面.而线程池则解决了这个问题,线程池的作用就是将线程进行复用. 1.2.JDK为我们提供了哪些支持 JDK中的相关类图如上图所示. 其中要提到的几个特别的类. Callable类和Runable类相似,但

  • Java 高并发九:锁的优化和注意事项详解

    摘要 本系列基于炼数成金课程,为了更好的学习,做了系列的记录. 本文主要介绍: 1. 锁优化的思路和方法 2. 虚拟机内的锁优化 3. 一个错误使用锁的案例 4. ThreadLocal及其源码分析 1. 锁优化的思路和方法 在[高并发Java 一] 前言中有提到并发的级别. 一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点. 这里提到的锁优化,是指在阻塞式的情况下,如何让性能不要变得太差.但是再怎么优化,一般来说性能都会比无锁的情况差一点. 这里要注意的是,在[高并

  • 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

  • Java高并发系统限流算法的实现

    目录 1 概述 2 计数器限流 2.1 概述 2.2 实现 2.3 结果分析 2.4 优缺点 2.5 应用 3 漏桶算法 3.1 概述 3.2 实现 3.3 结果分析 3.4 优缺点 4 令牌桶算法 4.1 概述 4.2 实现 4.3 结果分析 4.4 应用 5 滑动窗口 5.1 概述 5.2 实现 5.3 结果分析 5.4 应用 1 概述 在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.限流可以认为服务降级的一种,限流是对系统的一种保护措施.即限制流量请求的频率(每秒处理多少个请求

随机推荐