Question

i'm using some ReentrantLock to synchronize access to a List across multiple threads. I just write a generic

try {
   lock.lock();
   ... modify list here
} finally {
   lock.unlock();
}

everywhere. I just noticed though, that most of lists across the code are going to be used by a single (gui dispatch-) thread only.

Now i'm not sure if i should remove the lock in these cases, as it might speed up my code. How fast is ReentrantLock? Is the lock() operation somehow faster if the thread himself was the "prior owner" of the lock, even if he unlock() it?

Was it helpful?

Solution

No matter how fast it is, it would certainly be slower than no locking, if just by a very small amount. I would personally prefer correctness over speed gain (which won't be a lot) and keep the code the way it is (especially if it is already tested) unless you have identified "locking" to be a bottleneck.

OTHER TIPS

3 things:

  • lock.lock() should be outside the try block.

  • if you run on a single core CPU locking is very cheap. Then depending on the CPU architecture the acquire/release might be cheap or not so much. Nehalem+ is sorta ok.

  • if you do not need locks for anything else synchronized could be a better approach as the JVM can coarsen the monitors and/or bias lock() 'em in a single threaded application. Again the performance of biased locks greatly varies on the CPU architecture.

If there is no contention, acquiring and releasing locks is quite inexpensive. I'd say you needn't worry about the performance implications.

I really don't understand if your application is single- or multi-threaded. The title says one thing, and from the body we can infer another thing. I assume that you are using more than one thread (otherwise, the question makes no sense -- why would you use synchronization between ... less than 2 threads?!).

There might be a bigger issue than performance. Visibility.

You don't give any details about the part of the code in which you ...modify a list..., however it is very important to know the details: depending on how you've done that, if you remove the locking what might happen is: one thread modifies the list and the other thread nevers sees these modifications (or sees partial, most probably inconsistent modifications).

The aspect of synchronization that it seems that you are missing is that, unless using some specific constructs (locks / volatile variables / final fields), there are no guarantees that one thread will see what another thread did to the memory.

In this case, your guarantee is given by the locks: when a thread T1 acquires a lock L, it is guaranteed that it will see all the modifications made to the memory by a thread T2 before T2 released the lock L.

   T2            T1
acquires L
modifies a
modifies b
releases L
modifies c    acquires L
              reads a
              reads b
              reads c
              releases L

In this case, it is guaranteed that T1 will see the correct values for a and b, but there's no guarantee about what it will see when reading c.

If you take out your locks, be sure that your data structure is thread-safe and, if the data contained (ie, your objects inside the list) is not thread-safe, be sure that between each modification and subsequent retrieval of data you trigger a happens-before relation, otherwise bad things will happen.

I wrote the test code which shows me that the ReentrantLock is 10 times slower than synchronized on a single thread.

Since my decoder of a custom TCP protocol spends spends 300 ns the speed of locking matters.

JDK 14:

  • ReentrantLock: 32.34 ns
  • synchronized: 3.48 ns

JDK 11:

  • ReentrantLock: 30.3 ns
  • synchronized: 3.7 ns

My test:

public class MyTest
{
    private ReentrantLock lock = new ReentrantLock();
    private String s;

    @Test
    public void test()
    {
        long t1 = System.currentTimeMillis();
        for (int i = 0; i < 100_000_000; i++) {
            lock.lock();
            try {
                //
            } finally {
                lock.unlock();
            }
        }
        long t2 = System.currentTimeMillis();
        long d1 = t2 - t1;
        System.out.println("ReentrantLock: " + d1 / 100f + " ns");

        t1 = System.currentTimeMillis();
        for (int i = 0; i < 100_000_000; i++) {
            m1();
        }
        t2 = System.currentTimeMillis();
        long d2 = t2 - t1;
        t1 = System.currentTimeMillis();
        for (int i = 0; i < 100_000_000; i++) {
            m2();
        }
        t2 = System.currentTimeMillis();
        long d3 = t2 - t1;
        long d4 = d2 - d3;
        System.out.println("synchronized: " + d4 / 100f + " ns");
    }

    public synchronized void m1() {
        s = "1";
        s = "2";
        s = "3";
    }

    public void m2() {
        s = "1";
        s = "2";
        s = "3";
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top