Home  >  Q&A  >  body text

java lock-free concurrency

Is the ratio of lock-free and lock-free implementation in the following code better? I use jmeter to make 20 requests per second. Most of the output of the sleep operation in test() without lock code is hugely different from 500 milliseconds, while the output of lock code is basically 500 milliseconds with a difference of 1,2 Milliseconds, this problem is very strange....

@Controller
@RequestMapping("/bench/")
public class BenchController {

    @Autowired
    private FlowService flowService;

    private static Object[] lockObj;
    private static AtomicReference<Integer>[] locks;

    static {
        lockObj = new Object[100];
        for (int i = 0; i < lockObj.length; i++) {
            lockObj[i] = new Object();
        }

        locks = new AtomicReference[100];
        for (int i = 0; i < locks.length; i++) {
            locks[i] = new AtomicReference<Integer>(null);
        }
    }

    @RequestMapping("a")
    @ResponseBody
    public long a(int id) throws Exception {
        long start = System.currentTimeMillis();
        int index = id % 100;
        long inner=0;
        synchronized (lockObj[index]) {
            inner=test();
        }
        long result = System.currentTimeMillis() - start;
        System.out.println("all: "+result+" inner: "+inner);
        return result;
    }

    @RequestMapping("b")
    @ResponseBody
    public long b(int id) throws Exception {
        long start = System.currentTimeMillis();
        AtomicReference<Integer> lock=locks[id % 100];
        while (!lock.compareAndSet(null, id)) {}
        long inner=test();
        boolean flag=lock.compareAndSet(id, null);
        long result = System.currentTimeMillis() - start;
        System.out.println("all: "+result+" inner: "+inner+" flag:"+flag);
        return result;
    }

    public long test()throws Exception{
        long innerstart = System.currentTimeMillis();
        Thread.sleep(500);
        System.out.println(System.currentTimeMillis()-innerstart);
        return System.currentTimeMillis()-innerstart;
    }
}
我想大声告诉你我想大声告诉你2713 days ago689

reply all(1)I'll reply

  • 曾经蜡笔没有小新

    曾经蜡笔没有小新2017-05-17 10:03:02

    1. First of all, let’s clarify two issues. Synchronized is generally not compared with the AtomicXX class, but more with the ReentrantLock class. There are many comparisons between the two on the Internet, you can google them by yourself.

    2. Regarding the question about no lock or lock, the code in test code b is problematic,

    • For method a, synchronized code block, after the lock is held by the first incoming thread, subsequent threads requesting to acquire the lock will be blocked and suspended. Until the previous thread releases the lock, subsequent threads will resume execution. Due to the existence of the lock, 20 requests are similar to sequential execution, this layer is scheduled by jvm

    • For method b, cas operation is non-blocking. The while loop in the method is actually always executing (continuously trying to perform cas operation), and we know that the infinite loop will consume CPU resources. The more concurrency, The more threads, the more CAS operations here, which will inevitably lead to a surge in CPU usage. When the code in method B is tested by jmeter, theoretically there should always be 20 active working threads. The CPU and thread model are another Topic, thread number tuning is a relatively advanced topic in jvm. If you are interested, you can google it by yourself

    • Let’s talk about ReentrantLock and synchronized: Usually under high concurrency, ReentrantLock has better performance than synchronized, and ReentrantLock provides some functions that synchronized does not provide (automatic abandonment of lock timeout, etc.). Sleep can be reduced in the sample code time, thereby simulating shorter pauses and higher concurrency. 500ms is very short for humans, but it is basically an astronomical number for CPUs. Basically, it is not an exaggeration to describe it as "slow as a snail". Modify the class and give an example. Code:

    package com.gzs.learn.springboot;
    
    import java.util.LinkedList;
    import java.util.Random;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.locks.LockSupport;
    import java.util.concurrent.locks.ReentrantLock;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    @RequestMapping("/bench/")
    public class BenchController {
        private Random random = new Random();
        private static Object[] lockObj;
        private static AtomicReference<Integer>[] locks;
        private static ReentrantLock[] reentrantLocks;
        static {
            lockObj = new Object[100];
            for (int i = 0; i < lockObj.length; i++) {
                lockObj[i] = new Object();
            }
    
            locks = new AtomicReference[100];
            for (int i = 0; i < locks.length; i++) {
                locks[i] = new AtomicReference<Integer>(null);
            }
            reentrantLocks = new ReentrantLock[100];
            for (int i = 0; i < reentrantLocks.length; i++) {
                reentrantLocks[i] = new ReentrantLock();
            }
        }
    
        @RequestMapping("a/{id}")
        @ResponseBody
        public long a(@PathVariable("id") int id) throws Exception {
            long start = System.currentTimeMillis();
            int index = id % 100;
            long inner = 0;
            synchronized (lockObj[index]) {
                inner = test();
            }
            long result = System.currentTimeMillis() - start;
            System.out.println("all: " + result + " inner: " + inner);
            return result;
        }
    
        @RequestMapping("b/{id}")
        @ResponseBody
        public long b(@PathVariable("id") int id) throws Exception {
            long start = System.currentTimeMillis();
            id = id % 100;
            AtomicReference<Integer> lock = locks[id];
            int b = 0;
            while (!lock.compareAndSet(null, id)) {
                b = 1 + 1;
            }
            long inner = test();
            boolean flag = lock.compareAndSet(id, null);
            long result = System.currentTimeMillis() - start;
            System.out.println("all: " + result + " inner: " + inner + " flag:" + flag);
            System.out.println(b);
            return result;
        }
    
        @RequestMapping("c/{id}")
        @ResponseBody
        public long c(@PathVariable("id") int id) throws Exception {
            long start = System.currentTimeMillis();
            id = id % 100;
            ReentrantLock lock = reentrantLocks[id];
            lock.lock();
            long inner = test();
            lock.unlock();
            long result = System.currentTimeMillis() - start;
            System.out.println("all: " + result + " inner: " + inner);
            return result;
        }
    
        public long test() throws Exception {
            long innerstart = System.currentTimeMillis();
            Thread.sleep(0, 100);
            // Thread.sleep(500);
            System.out.println(System.currentTimeMillis() - innerstart);
            return System.currentTimeMillis() - innerstart;
        }
    }
    
    • Method c is implemented using ReentrantLock. In most cases, ReentrantLock is more efficient than synchronized

    • The core class Aqs (AbstractQueuedSynchronizer) in
    • juc (java.util.concurrent) is a queue-based concurrency package. By default, the thread will be parked (suspend operation) when the lock competition (spin) exceeds 1000 nanoseconds. In order to reduce the frequent thread switching of the CPU, you can try to adjust the sleep time parameter in method c.

    • Test method, jmeter is not installed on this machine, test is done with apache ab, test command:

    ab -n 100 -c 20  http://localhost:8080/bench/a/10

    reply
    0
  • Cancelreply