多线程与高并发编程(九)

多线程与高并发编程(九)

    • 一、JMH(测试方法工具)简单介绍
      • 1. 测试准备步骤
      • 2. 注解
    • 二、Disruptor
      • 1. 特点:无锁高并发,使用环形Buffer,直接覆盖(不清除)旧的数据,降低GC频率,实现了基于事件的生产者消费者模式(观察者模式)。
      • 2. RingBuffer结构:disruptor是利用数组实现的,而且是首尾相连的环形结构(RingBuffer),内部维护一个sequence(代表下一个有效元素的位置),就这一个指针然后旋转指向下一个位置,加了一圈之后再加就会覆盖原有
      • 3. 使用
      • 4. 生产者线程模式:
      • 5. 等待策略
      • 6. 多个消费者模式
      • 7. 出异常情况处理

一、JMH(测试方法工具)简单介绍

链接: http://openjdk.java.net/projects/code-tools/jmh/.
JMH是基于注解的测试插件

1. 测试准备步骤

  引入maven依赖
  IDEA安装 JMH plugin插件
  打开运行程序注解配置setting->Build,…-> compiler -> Annotation Processors -> Enable Annotation Processing
  定义一个类PS 处理计算
  写单元测试类PSTest(运行类) 【一定写在Test-java目录下在这里插入图片描述
才可以运行】
  直接run运行PSTest
  若出现如下异常(默认要往C盘写入测试报告,对于windows而言很私密目录不允许写)

ERROR: org.openjdk.jmh.runner.RunnerException: ERROR: Exception while trying to acquire the JMH lock (C:\WINDOWS\/jmh.lock): C:\WINDOWS\jmh.lock (拒绝访问。), exiting. Use -Djmh.ignoreLock=true to forcefully continue.
at org.openjdk.jmh.runner.Runner.run(Runner.java:216)
at org.openjdk.jmh.Main.main(Main.java:71)

  点开Edit configurations->Environment variables点开->include system…勾上 (加载进入系统环境变量,系统环境变量中的TEMP目录中去写)
  重新运行成功

2. 注解

  @Benchmark 启用JMH测试
  @Warmup(iterations = 1, time = 3) 预热,虚拟机先起来,调用iterations次方法,每调一次等待time秒钟,因为jvm对特定代码会优化所以预热很重要
  @Fork(nums) 起nums个线程去执行这个程序
  @BenchmarkMode(Mode.Throughput) 执行模式,左侧是吞吐量(每秒执行多少次)
  @Measurement(iterations = 1, time = 3) 整个测试重复iterations次,每调一次等待time秒

二、Disruptor

  速度最快的MQ,性能极高,内部全是cas,单机(内存中的高效率队列,跟redis)

1. 特点:无锁高并发,使用环形Buffer,直接覆盖(不清除)旧的数据,降低GC频率,实现了基于事件的生产者消费者模式(观察者模式)。

  对比ConcurrentLinkedQueue(链表实现),在遍历上来讲链表性能低于数组。
  JDK中没有ConcurrentArrayQueue(数组实现),因为数组的长度是固定的,每次增加长度实际上都是新建一个更长的数组然后将数据复制进来。

2. RingBuffer结构:disruptor是利用数组实现的,而且是首尾相连的环形结构(RingBuffer),内部维护一个sequence(代表下一个有效元素的位置),就这一个指针然后旋转指向下一个位置,加了一圈之后再加就会覆盖原有

多线程与高并发编程(九)_第1张图片
多线程与高并发编程(九)_第2张图片

  总结:二进制的计算速度更快,而且转了一圈过后只会覆盖元素,所以没必要像链表一样维护头尾两个指针,只要维护一个就行了,综上所述ringBuffer的性能更强。
  这里的覆盖只是原理是这样,实际上在生产中生产满了,当下一个即将覆盖的时候,发现消费者还没取走第一个商品,则不继续生产覆盖第一个,执行等待策略(一共八种,最常见的为BlockingWait,阻塞等待),等什么时候消费者拿走了数据然后唤醒生产填补空位。
  这个sequence指针的实现源码有一段:
在这里插入图片描述

,前面声明7个long类型(8字节),后面声明7个long,保证cursor不管跟前面的对齐还是后面的对齐,一定是自己在一个缓存行内,所以效率十分高。

3. 使用

  环形队列中的元素实际上是一个个Event对象的引用。
  在new出来这个disruptor之后,会提前分配内存空间。
 步骤:
  定义Event,环形队列中的元素(生产的产品)
  定义Event工厂,用于填充队列。
  定义EventHandler(消费者),处理容器中的元素

public class LongEvent {
    private long value;

    public long getValue() {
        return value;
    }

    public void setValue(long value) {
        this.value = value;
    }
}
public class LongEventFactory implements EventFactory {
    public LongEvent newInstance() {
        return new LongEvent();
    }
}
public class LongEventHandler implements EventHandler {
    public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception {
        System.out.println(longEvent.getValue());
    }
}
public class LongEventProducer {
    private final RingBuffer ringBuffer;

    public LongEventProducer(RingBuffer ringBuffer) {
        this.ringBuffer = ringBuffer;
    }

    public void onData(ByteBuffer buffer) {
        long sequence = ringBuffer.next();
        try {
            LongEvent event = ringBuffer.get(sequence);
            event.setValue(buffer.getLong(0));
        } finally {
            ringBuffer.publish(sequence);
        }
    }

  上为声明准备类,下为主方法:

public static void main(String[] args) {
    //Executor executor = Executors.newCachedThreadPool();

    LongEventFactory factory = new LongEventFactory();

    //must be power of 2
    int ringBufferSize = 1024;

    Disruptor disruptor = new Disruptor(factory, ringBufferSize, Executors.defaultThreadFactory());

    disruptor.handleEventsWith(new LongEventHandler());

    disruptor.start();

    RingBuffer ringBuffer = disruptor.getRingBuffer();

    LongEventProducer producer = new LongEventProducer(ringBuffer);

    ByteBuffer bb = ByteBuffer.allocate(8);

    for(long l = 0; l<100; l++) {
        bb.putLong(0, l);

        producer.onData(bb);

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    disruptor.shutdown();
}

4. 生产者线程模式:

  ProducerType生产者线程模式一共有2种:
  ProducerType有两种模式 Producer.MULTI和Producer.SINGLE
  默认是MULTI,表示在多线程模式下产生sequence
  如果确认是单线程生产者,那么可以指定SINGLE,效率会提升
  如果是多个生产者(多线程),但模式指定为SINGLE,会出什么问题呢?
在这里插入图片描述

5. 等待策略

  ①,(常用)BlockingWaitStrategy:通过线程阻塞的方式,等待生产者唤醒,被唤醒后,再循环检查依赖的sequence是否已经消费
  ② BusySpinWaitStrategy:线程一直自旋等待,可能比较耗cpu
  ③ LiteBlockingWaitStrategy:线程阻塞等待生产者唤醒,与BlockingWaitStrategy相比,区别在signalNeeded.getAndSet,如果两个线程同时访问一个访问waitfor,一个访问signalAll时,可以减少lock加锁次数
  ④ LiteTimeoutBlockingWaitStrategy:与LiteBlockingWaitStrategy相比,设置了阻塞时间,超过时间后抛异常
  ⑤ PhasedBackoffWaitStrategy:根据时间参数和传入的等待策略来决定使用哪种等待策略
  ⑥ TimeoutBlockingWaitStrategy:相对于BlockingWaitStrategy来说,设置了等待时间,超过后抛异常
  ⑦ (常用)YieldingWaitStrategy:尝试100次,然后Thread.yield()让出cpu
  ⑧ (常用)SleepingWaitStrategy : sleep

6. 多个消费者模式

    LongEventHandler h1 = new LongEventHandler();
    LongEventHandler h2 = new LongEventHandler();
    disruptor.handleEventsWith(h1, h2...)//传入多个消费者(多线程)

7. 出异常情况处理

    EventHandler h1 = (event, sequence, end) -> {
        sout(event);
        throw new Exception("消费者出异常");
    }
    disruptor.handleEventsWith(h1);

    disruptor.handleExceptionsFor(h1).with(new ExcetionHandler()     {
        @Override
        public void handleEventExceition(Throwable throwable, long l, longEvent longEvent) {
            throwable.printStackTrace();        
        }

            @Override
        public void handleOnStartExceition(Throwable throwable) {
                sout("Exception Start to Hanled!");      
        }

        @Override
        public void handleOnShutDownExceition(Throwable throwable) {
                sout("Exception shutDown to Hanled!");      
        }
    })

  调用.handleExceptionsFor(h1).with()并重写以上三个方法,方法内处理。

你可能感兴趣的:(多线程与高并发,java,多线程)