"Java Concurrent Programming Practical Combat" Reading Notes 5 Series: 02-Pitfalls of Concurrent Program Testing

Concurrent program testing

1: 34.98+55.5 + 9.71 100.19

2:55.5 +9.71 65.21

3: 9.716

Pitfalls of concurrent program testing

  • Garbage collection
    • Unpredictable garbage collection operations
    • Solution:
      • When the test is running, the GC is not executed once
      • When the test is running, the GC runs multiple times and it takes a long enough time (several minutes) [better]
  • Dynamic compilation
    • JVM is a combination of interpretation execution and compilation execution. When a class is executed for the first time, the JVM will execute it by interpreting bytecode. At a certain moment, if a method is run enough times, the dynamic compiler will compile it into machine code, so that the code is Interpretation execution becomes compilation execution (because compilation execution is faster). In addition, the code may be decompiled and recompiled, and the compilation process is also time-consuming.
    • Solution:
      • Run for a long enough time, so that the compilation time and interpretation execution time can be ignored
      • Run the code for a period of time before starting the test to ensure that the code has been completely compiled before the test
      • When running the program, use the command line option: -xx: +PrintCompilation, a message will be output when the dynamic compilation is running. This message can be used to verify that the dynamic compilation is executed before the test is run.
  • Useless code elimination
    • It will run better in -server mode than -client mode, because -server mode is easier to eliminate useless code through optimization. If the code we want to test is eliminated as useless code, the test will be meaningless.
    • Solution: calculating a hash value of the object field, and it System.nanoTime()compares
      • if (foo.x.hashCode() == System.nanoTime()) System.out.print(" ");
  • Unreal sampling of the code path
    • At runtime, the compiler will optimize the compiled code according to the collected information, which means that the code generated when compiling method M of a certain program may be different from the code generated by compiling method M of another program. Create test gaps.
  • Unreal competition

How to check the correctness of a multi-producer, multi-consumer model

  • Maintain two AtomicInteger: putSumandtakeSum
  • Put the number in the queue and add it to putSum
  • Take the number out of the queue and add it to takeSum
  • an examination: putSum == takeSum
  • By CyclicBarrierconcurrent threads guarantee
public class PutTakeTest extends TestCase {
    
    
    protected static final ExecutorService pool = Executors.newCachedThreadPool();
    protected CyclicBarrier barrier;
    protected final SemaphoreBoundedBuffer<Integer> bb;
    protected final int nTrials, nPairs;
    protected final AtomicInteger putSum = new AtomicInteger(0);
    protected final AtomicInteger takeSum = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
    
    
        new PutTakeTest(10, 10, 100000).test(); // sample parameters
        pool.shutdown();
    }

    public PutTakeTest(int capacity, int npairs, int ntrials) {
    
    
        this.bb = new SemaphoreBoundedBuffer<Integer>(capacity);
        this.nTrials = ntrials;
        this.nPairs = npairs;
        // 使用CyclicBarrier保证生产者和消费者线程的并发执行
        this.barrier = new CyclicBarrier(npairs * 2 + 1);
    }

    void test() {
    
    
        try {
    
    
            for (int i = 0; i < nPairs; i++) {
    
    
                pool.execute(new Producer());
                pool.execute(new Consumer());
            }
            barrier.await(); // 阻塞主线程,等待所有线程就绪
            barrier.await(); // 等待所有线程执行完成,
                             // 即生产者和消费者线程中的第二个await执行完
                             // 相当于循环使用了两次CyclicBarrier
            assertEquals(putSum.get(), takeSum.get());
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }
    
    // 测试用的随机数生成代码
    // 使用方法:通过前一个seed生成后一个seed
    // int seed = (this.hashCode() ^ (int) System.nanoTime());
    // seed = xorShift(seed);
    static int xorShift(int y) {
    
     
        y ^= (y << 6);
        y ^= (y >>> 21);
        y ^= (y << 7);
        return y;
    }

    class Producer implements Runnable {
    
    
        public void run() {
    
    
            try {
    
    
                int seed = (this.hashCode() ^ (int) System.nanoTime());
                int sum = 0;
                barrier.await(); // 通知主线程生产者线程开始执行准备就绪
                for (int i = nTrials; i > 0; --i) {
    
    
                    bb.put(seed);
                    sum += seed;
                    seed = xorShift(seed);
                }
                putSum.getAndAdd(sum);
                barrier.await(); // 通知主线程生产者线程执行结束
            } catch (Exception e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }

    class Consumer implements Runnable {
    
    
        public void run() {
    
    
            try {
    
    
                barrier.await(); // 通知主线程消费者线程开始执行准备就绪
                int sum = 0;
                for (int i = nTrials; i > 0; --i) {
    
    
                    sum += bb.take();
                }
                takeSum.getAndAdd(sum);
                barrier.await(); // 通知主线程消费者线程执行结束
            } catch (Exception e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
}

Blocking queue performance comparison

  • LinkedBlockingQueueThe scalability is better than ArrayBlockingQueue.
  • Contrary to common sense: because the linked list element needs to be assigned a linked list node object when inserting an element, it should be more time-consuming in theory.
  • Reason: Compared with array-based queues, the put and take methods of linked list queues support higher access, because some optimized linked list queues can separate the update operation of the head node of the queue from the update operation of the tail node.

Guess you like

Origin blog.csdn.net/weixin_43314519/article/details/113571608