4

I'm considering switching from synchronized to a ReadWriteLock. Before doing it, I would like to check if it's worth it.

ThreadMXBean and ThreadInfo provide information about overall thread blocked count and time. Those blocks can be caused by multiple monitors. Is there a way to measure block statistics given a particular monitor object?

Konstantin Milyutin
  • 11,946
  • 11
  • 59
  • 85
  • thanks a lot, I was only familiar with jconsole and visualvm and they don't provide lock contention info. jmc looks just like what I want. Could you post your answer below? – Konstantin Milyutin Jul 05 '16 at 20:07

3 Answers3

4

Yes, it is possible using JVMTI.

What is you need is to write a native agent that handles a pair of events:

Both events accept jthread and jobject arguments that correspond to a thread acquiring the monitor and the monitor object itself.


Here is a sample code for the contention profiler agent.

apangin
  • 92,924
  • 10
  • 193
  • 247
3

jmc, yourkit and jprofiler all provide lock contention profiling.

the8472
  • 40,999
  • 5
  • 70
  • 122
1

Internal kernel of ReentrantReadWriteLock - private field 'sync' of type AbstractQueuedSynchronizer (AQS). AQS - one of greatest 'synchronizers' of Doug Lea. It contains single-linked list of contended Threads. Every Thread marked is it 'exclusive' or 'shared'. Read for more info "The java.util.concurrent Synchronizer Framework".

You can periodically examine blocked thread queue (100 times in second) and collect stats.

import java.lang.reflect.Field;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class App {
    static final AtomicInteger freeTime = new AtomicInteger(0);
    static final AtomicInteger fullTime = new AtomicInteger(0);
    static final AtomicInteger readersLength = new AtomicInteger(0);
    static final AtomicInteger writersLength = new AtomicInteger(0);

    public static float contended() {
        return fullTime.get() / (fullTime.get() + freeTime.get());
    }

    public static float uncontended() {
        return freeTime.get() / (fullTime.get() + freeTime.get());
    }

    public static float meanReadersQueue() {
        return readersLength.get() / fullTime.get();
    }

    public static float meanWritersQueue() {
        return writersLength.get() / fullTime.get();
    }

    public static void main(String[] args) throws Exception {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
        AbstractQueuedSynchronizer sync =
                useReflection(lock, "sync", AbstractQueuedSynchronizer.class);

        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
            int queueLength = sync.getQueueLength();
            if (queueLength == 0) {
                freeTime.incrementAndGet();
            } else {
                fullTime.incrementAndGet();
                int readersCount = sync.getSharedQueuedThreads().size();
                readersLength.addAndGet(readersCount);
                int writersCount = sync.getExclusiveQueuedThreads().size();
                writersLength.addAndGet(writersCount);
            }
        }, 0, 10, TimeUnit.MILLISECONDS);
    }

    private static <T> T useReflection(Object from, String name, Class<T> to) throws Exception {
        Field f = from.getClass().getDeclaredField(name);
        f.setAccessible(true);
        return (T) f.get(from);
    }
}
Ivan Golovach
  • 199
  • 2
  • 5