Java多线程(二)Java并发工具包concurrent实例简述

传统的多线程并没有提供高级特性,例如:信号量、线程池和执行管理器等,而这些特性恰恰有助于创建强大的并发程序。新的Fork/Join框架针对当前的多核系统,也提供了并行编程的可能。这块的内容是java多线程信息量最大的一部分内容,本篇博客循序渐进的,首先对concurrent包下的各模块常用类和接口进行实例展示,从使用中去了解concurrent包的内容。

1.Java并发工具的体系结构

Java多线程(二)Java并发工具包concurrent实例简述_第1张图片
java并发工具包处于java.util.concurrent包中,从上图中可以看出主要包括同步器、执行器、并发集合、Fork/Join框架、atomic包、locks包。
简单来说如下所示:

  • tools同步器:为每种特定的同步问题提供了解决方案
  • executor 执行器:用来管理线程的执行
  • collections并发集合:提供了集合框架中集合的并发版本
  • atomic包:提供了不需要锁既可以完成并发环境使用的原子性操作
  • locks包:使用Lock接口为并发编程提供了同步的另外一种替代方案
  • Fork/Join框架:提供了对并行编程的支持

2.同步器

2.1 Semaphore信号量

描述了一个在操作系统当中比较经典的信号量,主要作用是通过计数器来控制对共享资源的访问。

  • 常用API
  1. Semaphore(int count):构造函数,创建拥有count个许可证的信号量。
  2. acquire()/acquire(int num):获取1/num个许可证
  3. release()/release(int num):释放1/num个许可证
  • 实例使用
import cn.ji2h.util.LogUtil;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

    public static void main(String[] args){
    	//定义两个许可证
        Semaphore semaphore = new Semaphore(2);

        Persion p1 = new Persion(semaphore,"A");
        p1.start();

        Persion p2 = new Persion(semaphore,"B");
        p2.start();

        Persion p3 = new Persion(semaphore,"C");
        p3.start();
    }

}

class Persion extends Thread{
    private Semaphore semaphore;

    public Persion(Semaphore semaphore,String name){
        setName(name);
        this.semaphore = semaphore;
    }

    public void run(){
        LogUtil.logger.info(getName() + " is waiting ......");
        try {
            semaphore.acquire();
            LogUtil.logger.info(getName() + " is servicing ......");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LogUtil.logger.info(getName() + " is done!");
        semaphore.release();
    }
}

模拟银行柜员服务的例子,三个客户,两个柜台。对应的三个线程,两个资源。

  • 运行结果
2018-10-02 16:28:41,229 [C] INFO  cn.ji2h.util.LogUtil - C is waiting ......
2018-10-02 16:28:41,229 [A] INFO  cn.ji2h.util.LogUtil - A is waiting ......
2018-10-02 16:28:41,229 [B] INFO  cn.ji2h.util.LogUtil - B is waiting ......
2018-10-02 16:28:41,231 [A] INFO  cn.ji2h.util.LogUtil - A is servicing ......
2018-10-02 16:28:41,231 [C] INFO  cn.ji2h.util.LogUtil - C is servicing ......
2018-10-02 16:28:42,235 [C] INFO  cn.ji2h.util.LogUtil - C is done!
2018-10-02 16:28:42,235 [A] INFO  cn.ji2h.util.LogUtil - A is done!
2018-10-02 16:28:42,236 [B] INFO  cn.ji2h.util.LogUtil - B is servicing ......
2018-10-02 16:28:43,240 [B] INFO  cn.ji2h.util.LogUtil - B is done!

2.2 CountDownLatch同步器

必须发生指定数量的时间后才可以继续执行

  • 常用API
  1. CountDownLatch(int count):必须发生count个数量事件才可以打开锁存器
  2. await():等待锁存器
  3. countDown():触发事件
  • 实例使用

import cn.ji2h.util.LogUtil;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args){
        //创建计数栓
        CountDownLatch countDownLatch = new CountDownLatch(3);
        //创建三个线程
        new Racer(countDownLatch,"A").start();
        new Racer(countDownLatch,"B").start();
        new Racer(countDownLatch,"C").start();

        //开始倒计时3...2...1...
        for(int i = 0; i<3 ;i++){
            try{
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LogUtil.logger.info((3 - i)+ "");
            if(i == 2){
                LogUtil.logger.info("Start");
            }
            //出发执行起跑
            countDownLatch.countDown();
        }

    }
}

class Racer extends Thread{
    private CountDownLatch countDownLatch;

    public Racer(CountDownLatch countDownLatch,String name){
        setName(name);
        this.countDownLatch = countDownLatch;
    }

    public void run(){
        try{
            countDownLatch.await();
            for (int i = 0;i<3 ;i ++){
                LogUtil.logger.info(getName() + " run : " + i);
            }
        } catch(InterruptedException e){
            e.printStackTrace();
        }

    }

}

模拟倒计数3-2-1-start后,三个线程开始跑步的例子。

  • 运行结果
2018-10-02 16:37:23,316 [main] INFO  cn.ji2h.util.LogUtil - 3
2018-10-02 16:37:24,323 [main] INFO  cn.ji2h.util.LogUtil - 2
2018-10-02 16:37:25,324 [main] INFO  cn.ji2h.util.LogUtil - 1
2018-10-02 16:37:25,324 [main] INFO  cn.ji2h.util.LogUtil - Start
2018-10-02 16:37:25,324 [A] INFO  cn.ji2h.util.LogUtil - A : 0
2018-10-02 16:37:25,324 [B] INFO  cn.ji2h.util.LogUtil - B : 0
2018-10-02 16:37:25,325 [B] INFO  cn.ji2h.util.LogUtil - B : 1
2018-10-02 16:37:25,325 [B] INFO  cn.ji2h.util.LogUtil - B : 2
2018-10-02 16:37:25,324 [A] INFO  cn.ji2h.util.LogUtil - A : 1
2018-10-02 16:37:25,324 [C] INFO  cn.ji2h.util.LogUtil - C : 0
2018-10-02 16:37:25,325 [C] INFO  cn.ji2h.util.LogUtil - C : 1
2018-10-02 16:37:25,325 [A] INFO  cn.ji2h.util.LogUtil - A : 2
2018-10-02 16:37:25,325 [C] INFO  cn.ji2h.util.LogUtil - C : 2

2.3 CyclicBarrier循环屏障同步器

适合于只有多个线程都到达与定点时才可以继续执行

  • 常用API
  1. CyclicBarrier(int num);等待线程的数量
  2. CyclicBarrier(int num, Runnable action):等待线程的数量以及所有线程到达后的操作
  3. await():到达临界点后暂停线程
  • 实例使用
import cn.ji2h.util.LogUtil;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args){
        //定义一个循环屏障,这定义了三个
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
            public void run() {
                LogUtil.logger.info("Game start!");
            }
        });

        //创建三个工作线程
        new Player(cyclicBarrier,"A").start();
        new Player(cyclicBarrier,"B").start();
        new Player(cyclicBarrier,"C").start();
    }
}

class Player extends Thread{
    private CyclicBarrier cyclicBarrier;
    public Player(CyclicBarrier cyclicBarrier,String name){
        setName(name);
        this.cyclicBarrier = cyclicBarrier;
    }

    public void run(){
        LogUtil.logger.info(getName() + " is waiting other players ...");

        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

模拟斗地主,三个人都准备好了再开始比赛的场景。每个线程在循环屏障处等待,主线程通过定义的循环屏障后继续执行。

2.4 Exchanger交换器同步器

简化两个线程间数据的交换

  • 常用API
  1. Exchanger:指定进行交换的数据类型
  2. V exchange(V object):等待线程到达,交换数据。
  • 实例使用
import cn.ji2h.util.LogUtil;

import java.util.concurrent.Exchanger;

public class ExchangerDemo {
    public static void main(String[] args){
        //定义一个交换器
        Exchanger exchanger = new Exchanger();

        //A或B谁先执行不确定
        //若A先执行则A-B-B-A-A-B执行顺序
        //若B先执行则B-A-A-B-B-A执行顺序
        new A(exchanger).start();
        new B(exchanger).start();
    }
}

class A extends Thread{
    private Exchanger exchanger;
    public A(Exchanger exchanger){
        this.exchanger = exchanger;
    }
    public void run(){
        String str = null;
        try {
            str = exchanger.exchange("Hello");
            LogUtil.logger.info(str);

            str = exchanger.exchange("A");
            LogUtil.logger.info(str);

            str = exchanger.exchange("B");
            LogUtil.logger.info(str);

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

    }
}


class B extends Thread{
    private Exchanger exchanger;
    public B(Exchanger exchanger){
        this.exchanger = exchanger;
    }
    public void run(){
        String str = null;
        try {
            str = exchanger.exchange("Hi");
            LogUtil.logger.info(str);

            str = exchanger.exchange("1");
            LogUtil.logger.info(str);

            str = exchanger.exchange("2");
            LogUtil.logger.info(str);

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

    }
}
  • 执行结果
2018-10-02 17:09:26,396 [Thread-1] INFO  cn.ji2h.util.LogUtil - Hello
2018-10-02 17:09:26,396 [Thread-0] INFO  cn.ji2h.util.LogUtil - Hi
2018-10-02 17:09:26,398 [Thread-0] INFO  cn.ji2h.util.LogUtil - 1
2018-10-02 17:09:26,398 [Thread-1] INFO  cn.ji2h.util.LogUtil - A
2018-10-02 17:09:26,398 [Thread-1] INFO  cn.ji2h.util.LogUtil - B
2018-10-02 17:09:26,398 [Thread-0] INFO  cn.ji2h.util.LogUtil - 2

2.5 Phsaer同步器

工作方式与CyclicBarrier类似,但是可以定义多个阶段

  • 常用API
  1. Phaser()/Phaser(int num):使用指定0/num个party创建Phaser
  2. register():注册party
  3. arriveAndAdvance():到达时等待所有party到达
  4. arriveAndDeregister():到达时注销线程自己
  • 实例使用
import cn.ji2h.util.LogUtil;

import java.util.concurrent.Phaser;

public class PhaserDemo {
    public static void main(String[] args){

        Phaser phaser = new Phaser(1);

        LogUtil.logger.info("Starting ...");

        //假设三个阶段
        new Worker(phaser,"服务员").start();
        new Worker(phaser,"厨师").start();
        new Worker(phaser,"上菜").start();

        for (int i = 1;i<= 3;i++){
            //一共三个订单,对于每个订单,必须要求三个阶段都完成才可以进行下个订单
            phaser.arriveAndAwaitAdvance();
            LogUtil.logger.info("order " + i + " finished!");
        }

        phaser.arriveAndDeregister();
        LogUtil.logger.info("all done!");
    }
}

class Worker extends Thread{
    private Phaser phaser;

    public Worker(Phaser phaser,String name) {
        this.setName(name);
        this.phaser = phaser;
        //将自己注册进来
        phaser.register();
    }

    public void run(){
        for (int i = 1;i<=3;i++){
            LogUtil.logger.info(" current order is : " + i + ":" + getName());

            if(i == 3){
                //都处理完了注销自己
                phaser.arriveAndDeregister();
            }else {
                //自己处理完了,等待其他的阶段处理
                phaser.arriveAndAwaitAdvance();
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 运行结果
2018-10-02 17:31:05,755 [main] INFO  cn.ji2h.util.LogUtil - Starting ...
2018-10-02 17:31:05,757 [服务员] INFO  cn.ji2h.util.LogUtil -  current order is : 1:服务员
2018-10-02 17:31:05,758 [厨师] INFO  cn.ji2h.util.LogUtil -  current order is : 1:厨师
2018-10-02 17:31:05,758 [上菜] INFO  cn.ji2h.util.LogUtil -  current order is : 1:上菜
2018-10-02 17:31:05,758 [main] INFO  cn.ji2h.util.LogUtil - order 1 finished!
2018-10-02 17:31:06,761 [上菜] INFO  cn.ji2h.util.LogUtil -  current order is : 2:上菜
2018-10-02 17:31:06,763 [厨师] INFO  cn.ji2h.util.LogUtil -  current order is : 2:厨师
2018-10-02 17:31:06,763 [服务员] INFO  cn.ji2h.util.LogUtil -  current order is : 2:服务员
2018-10-02 17:31:06,764 [main] INFO  cn.ji2h.util.LogUtil - order 2 finished!
2018-10-02 17:31:07,768 [厨师] INFO  cn.ji2h.util.LogUtil -  current order is : 3:厨师
2018-10-02 17:31:07,768 [服务员] INFO  cn.ji2h.util.LogUtil -  current order is : 3:服务员
2018-10-02 17:31:07,768 [上菜] INFO  cn.ji2h.util.LogUtil -  current order is : 3:上菜
2018-10-02 17:31:07,768 [main] INFO  cn.ji2h.util.LogUtil - order 3 finished!
2018-10-02 17:31:07,768 [main] INFO  cn.ji2h.util.LogUtil - all done!

3.执行器

3.1基本概念

  • 用于启动并控制线程的执行
  • 核心接口Executor,包含一个execute(Runnable)用于指定被执行的线程
  • ExecutorService接口用于控制线程执行和管理线程
  • 预定义了如下执行器:ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool

3.2 Callable与Future

Callable:表示具有返回值的线程,
V:表示返回值类型 
call():执行任务 

Future:表示Callable的返回值, 
V:返回值类型 
get():获取返回值
  • 实例使用
import cn.ji2h.util.LogUtil;

import java.util.concurrent.*;

public class ExecutorDemo {

    public static void main(String args[]){
        //定义执行器,固定了2个线程的一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future r1 = executorService.submit(new MC(1,100));
        Future r2 = executorService.submit(new MC(100,10000));
        try {
            LogUtil.logger.info(r1.get() + "   " + r2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        executorService.shutdown();
    }


}

class MC implements Callable {

    private int begin,end;
    public MC(int begin,int end){
        this.begin = begin;
        this.end = end;
    }

    @Override
    public Integer call() throws Exception {

        int sum = 0;

        for (int i= begin; i
  • 运行结果
2018-10-02 17:38:58,803 [main] INFO  cn.ji2h.util.LogUtil - 4950   49990050

4.锁

java.util.concurrent.locks包中提供了对锁的支持。锁的作用是为使用synchronized控制对资源访问提供了替代机制。

  1. 基本操作模型:访问资源之前申请锁,访问完毕之后释放锁。
  2. lock/tryLock:申请锁
  3. unlock:释放锁
  4. Lock是一个接口,具体锁类ReentrantLock实现了Lock接口 4 Lock是一个接口,具体锁类ReentrantLock实现了Lock接口
  • 使用实例
mport cn.ji2h.util.LogUtil;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {

    public static void main(String args[]){
        new MT().start();
        new MT().start();
        new MT().start();
        new MT().start();
    }

}


class Data{
    static int i = 0;

    static Lock lock = new ReentrantLock();

    static void operate(){
        lock.lock();
        i++;
        LogUtil.logger.info(i + "");
        lock.unlock();
    }
}

class MT extends Thread{
    public void run(){
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Data.operate();
        }
    }
}
  • 运行结果
2018-10-02 20:36:52,143 [Thread-0] INFO  cn.ji2h.util.LogUtil - 1
2018-10-02 20:36:52,144 [Thread-1] INFO  cn.ji2h.util.LogUtil - 2
2018-10-02 20:36:52,144 [Thread-2] INFO  cn.ji2h.util.LogUtil - 3
2018-10-02 20:36:52,144 [Thread-3] INFO  cn.ji2h.util.LogUtil - 4
2018-10-02 20:36:53,148 [Thread-0] INFO  cn.ji2h.util.LogUtil - 5
2018-10-02 20:36:53,149 [Thread-1] INFO  cn.ji2h.util.LogUtil - 6
2018-10-02 20:36:53,150 [Thread-2] INFO  cn.ji2h.util.LogUtil - 7
2018-10-02 20:36:53,150 [Thread-3] INFO  cn.ji2h.util.LogUtil - 8
2018-10-02 20:36:54,153 [Thread-0] INFO  cn.ji2h.util.LogUtil - 9
2018-10-02 20:36:54,154 [Thread-1] INFO  cn.ji2h.util.LogUtil - 10
2018-10-02 20:36:54,154 [Thread-2] INFO  cn.ji2h.util.LogUtil - 11
2018-10-02 20:36:54,154 [Thread-3] INFO  cn.ji2h.util.LogUtil - 12

5.原子操作

这里的实例是4中的变种
java.util.concurrent.atom包中提供了对原子操作的支持,原子操作主要是提供了不需要锁以及其他同步机制就可以进行的一些不可中断操作。通过原子操作,能够简化对锁的一些控制。主要操作为:获取、设置、比较等。

  • 使用实例
import cn.ji2h.util.LogUtil;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomDemo {
    public static void main(String args[]){
        new MT1().start();
        new MT1().start();
        new MT1().start();
        new MT1().start();
    }
}

class AtomData{
    static int i = 0;

    static AtomicInteger ai = new AtomicInteger(0);

    static void operate(){
        LogUtil.logger.info(ai.incrementAndGet() + "");
    }
}

class MT1 extends Thread{
    public void run(){
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            AtomData.operate();
        }
    }
}
  • 运行结果
2018-10-02 20:46:02,220 [Thread-3] INFO  cn.ji2h.util.LogUtil - 1
2018-10-02 20:46:02,220 [Thread-1] INFO  cn.ji2h.util.LogUtil - 4
2018-10-02 20:46:02,220 [Thread-0] INFO  cn.ji2h.util.LogUtil - 2
2018-10-02 20:46:02,220 [Thread-2] INFO  cn.ji2h.util.LogUtil - 3
2018-10-02 20:46:03,225 [Thread-1] INFO  cn.ji2h.util.LogUtil - 7
2018-10-02 20:46:03,225 [Thread-2] INFO  cn.ji2h.util.LogUtil - 8
2018-10-02 20:46:03,225 [Thread-0] INFO  cn.ji2h.util.LogUtil - 5
2018-10-02 20:46:03,225 [Thread-3] INFO  cn.ji2h.util.LogUtil - 6
2018-10-02 20:46:04,230 [Thread-2] INFO  cn.ji2h.util.LogUtil - 10
2018-10-02 20:46:04,230 [Thread-3] INFO  cn.ji2h.util.LogUtil - 12
2018-10-02 20:46:04,230 [Thread-0] INFO  cn.ji2h.util.LogUtil - 11
2018-10-02 20:46:04,230 [Thread-1] INFO  cn.ji2h.util.LogUtil - 9

线程执行顺序可忽视

6. 并发集合

这里我们使用Concurrent进行举例说明

ConcurrentHashMap 和 java.util.HashTable 类很相似,但 ConcurrentHashMap 能够提供比 HashTable 更好的并发性能。在你从中读取对象的时候 ConcurrentHashMap 并不会把整个 Map 锁住。此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。它的内部只是把 Map中正在被写入的部分进行锁定。

  • 使用实例
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class ConcurrentHashMapDemo {
    private static int INPUT_NUMBER = 100000;
    public static void main(String[] args) throws InterruptedException {
//        Map map = new Hashtable<>(12 * INPUT_NUMBER);
        Map map = new ConcurrentHashMap<>(12 * INPUT_NUMBER);
        long begin = System.currentTimeMillis();
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            service.execute(new InputWorker(map, i));
        }
        service.shutdown();
        service.awaitTermination(1, TimeUnit.DAYS);
        long end = System.currentTimeMillis();
        System.out.println("span time = "+(end-begin)+", map size = "+map.size());
    }

    private static class InputWorker implements Runnable {
        private static Random rand = new Random(System.currentTimeMillis());
        private final Map map;
        private final int flag;

        private InputWorker(Map map, int begin) {
            this.map = map;
            this.flag = begin;
        }

        @Override
        public void run() {
            int input = 0;
            while (input < INPUT_NUMBER) {
                int x = rand.nextInt();
                if (!map.containsKey(x)) {
                    map.put(x, "Alex Wang" + x);
                    input++;
                }
            }
            System.out.println("InputWorker" + flag + " is over.");
        }
    }
}
  • 运行结果
InputWorker1 is over.
InputWorker3 is over.
InputWorker4 is over.
InputWorker8 is over.
InputWorker2 is over.
InputWorker0 is over.
InputWorker6 is over.
InputWorker9 is over.
InputWorker7 is over.
InputWorker5 is over.
span time = 324, map size = 1000000

这段代码中,生成10个子线程,每个线程向map中插入10万个键值对,然后计算耗时。ConcurrentHashMap平均耗时324ms,Hashtable平均耗时680ms,如此看来在我的普通PC机(i7,4核,16G内存)上,ConcurrentHashMap的耗时仅占Hashtable耗时的不到一半。

7.Fork、Join框架

7.1 Fork/Join框架中的主要类

  • ForkJoinTask:描述任务的抽象类
  • ForkJoinPool:管理ForkJoinTask的线程池
  • RecursiveAction:ForkJoinTask子类,描述无返回值的任务
  • RecursiveTask:ForkJoinTask子类,描述有返回值的任务

7.2分而治之策略

  • 将任务递归划分成更小的子任务,直到子任务足够小,从而能够被连续地处理掉为止。
  • 优势是处理过程可以使用并行发生,这种情况特别适合基于多核处理器的并行编程
  • 根据Java API中定义,分而治之的建议临界点定义在100-1000个操作(加减乘除等)中的某个位置

7.3 计算1-1000000的和

  • 使用实例
import cn.ji2h.util.LogUtil;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

public class ForkJoinDemo {
    public static void main(String[] args){
        ForkJoinPool forkJoinPool  = new ForkJoinPool();
        Future result = forkJoinPool.submit(new MTask(0,1000001));
        try {
            LogUtil.logger.info(result.get() + "");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        forkJoinPool.shutdown();
    }

}


class MTask extends RecursiveTask{

    public static final int THRESHOLD = 1000;

    private int begin,end;

    public MTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long sum = 0;
        //判断任务是否满足条件1000个以内的操作
        if((end - begin) <= THRESHOLD){
            for(int i = begin;i
  • 运行结果
···
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-2] INFO  cn.ji2h.util.LogUtil - 999026-1000001:974525175
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-2] INFO  cn.ji2h.util.LogUtil - 998049-1000001:1949096799
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-2] INFO  cn.ji2h.util.LogUtil - 996096-1000001:3894383295
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-2] INFO  cn.ji2h.util.LogUtil - 992190-1000001:7773525378
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-2] INFO  cn.ji2h.util.LogUtil - 984377-1000001:15487070286
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-2] INFO  cn.ji2h.util.LogUtil - 968752-1000001:30730249947
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-2] INFO  cn.ji2h.util.LogUtil - 937502-1000001:60484937394
2018-10-02 21:30:01,688 [ForkJoinPool-1-worker-0] INFO  cn.ji2h.util.LogUtil - 843752-875001:26827999947
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-0] INFO  cn.ji2h.util.LogUtil - 812502-875001:52680437394
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-0] INFO  cn.ji2h.util.LogUtil - 750002-875001:101458624788
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-0] INFO  cn.ji2h.util.LogUtil - 875002-1000001:117067624788
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-3] INFO  cn.ji2h.util.LogUtil - 750002-1000001:218526249576
2018-10-02 21:30:01,689 [ForkJoinPool-1-worker-1] INFO  cn.ji2h.util.LogUtil - 500001-1000001:374616999162
2018-10-02 21:30:01,694 [main] INFO  cn.ji2h.util.LogUtil - 499488998835

8.参考链接

极客学院:https://www.jikexueyuan.com/course/java/

你可能感兴趣的:(Java知识)