0

This is probably a very silly question. I am reading through the documentation of interfaces and classes in packages java.util.concurrent.atomic and java.util.concurrent.locks. I find everywhere that

void lock() – acquire the lock if it’s available; if the lock isn’t available a thread gets blocked until the lock is released

What I am not sure about is, what resource is exactly getting locked? The code snippet examples show

Lock lock = new ReentrantLock(); 
lock.lock();
try {
    // access to the shared resource
} finally {
    lock.unlock();
}

Does whatever that is used under the call of lock() get locked? How does JVM know that before getting to that line? And what if there are multiple resources between lock() and unlock()? I guess I am having this question because I am reading this right after reading synchronization and it has very specific way of saying what to lock - like: synchronized(resourceReferenceThatNeedsToBeLocked)

I reseached a lot and yet can't find answer for this question.

subject-q
  • 91
  • 3
  • 19
  • 1
    Not a resource is being locked but the thread executing some instructions is prevented from continuing. A thread is nothing more than an independant execution of a sequence of instructions. Multiple threads might perform the same instructions or different ones, it doesn't really matter. If they want to access a criticial section guarded by the same lock they will compete for the lock. Threads that couldn't obtain a lock will wait till the lock gets available again and compete for it once again. This may lead to thread cogestions and in worst case to dead-locks. – Roman Vottner Mar 12 '19 at 14:00

3 Answers3

1

You can think of your code like an optimised version of synchronized. You are "synchronizing" on your lock object, but in a more efficient way.

Note that when you're using synchronized, there are no guarantees regarding the resources used inside of the synchronized block. You are just locking on a specific object, which may or may not be the same resources you are using inside of the synchronized block. In essence, regardless of lock or synchronized, you're just saying "make sure no other thread can access the code (or other code guarded by the same lock or ´synchronized´ on the same instance) inside of this block until I'm finished".

The key thing to understand, regardless of lock or synchronized, is that you're guarding a block of code from concurrent access. The code inside the block may access one or several different resources; if the same resources are used elsewhere, access to them needs to be guarded with the same lock or be synchronized on the same instance in order to be safe.

marthursson
  • 3,242
  • 1
  • 18
  • 28
  • can you add more detail please? Why would I want to synchronize a lock object, instead of my resource? – subject-q Mar 12 '19 at 12:41
  • Does that mean, all the mutable resources between `lock()` and `unlock()` are locked by the current thread? – subject-q Mar 12 '19 at 12:45
  • Added some more clarifications. – marthursson Mar 12 '19 at 12:54
  • Re, "...but in a more efficient way." That would be an implementation detail. Nothing prevents the JRE from implementing `synchronized` in the most efficient way possible. Using explicit `Lock` objects gives the developer more flexibility, but they are not inherently faster. – Solomon Slow Mar 22 '19 at 12:37
0

Lock is always associated with data. If there's no data, synchronization is pointless. You have object Thread, Lock, Condition and so on. And then you have data structure that is synchronized with the help of this objects. You need full example. In sample bellow, I'm synchronizing a queue, so that data added to it is always synchronized. Of course it's added to queue from different threads

import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;


public class BackgroundWorker {

    private Worker worker;
    private StringBuilder allData;

    @Before
    public void setUp() throws Exception {
        worker = new Worker(allData); // start databse worker
        worker.start();
    }

    @After
    public void tearDown() throws Exception {
        worker.stop();
    }

    public void logValue(final String data) {
        final LogValue logValue = new LogValue(data);
        worker.queue(logValue);
    }

    @Test
    public void test() {
        // very dummy, NOT multithreaded test 
        for (int i = 0; i < 10; i++) {
            logValue("Some data " + i);
        }
    }

    private static class Worker implements Runnable {

        private static final int MAX_QUEUE_SIZE = 1000;

        private final Deque<Job> queue = new LinkedList<Job>();
        private final Lock lock = new ReentrantLock();
        private final Condition jobAdded = lock.newCondition();
        private final Condition jobRemoved = lock.newCondition();
        private final StringBuilder dataSource;
        private final AtomicBoolean running = new AtomicBoolean(false);
        private Thread thread = null;

        Worker(final StringBuilder dataSource) {
            this.dataSource = dataSource;
        }

        @Override
        public void run() {

            processing: for (;;) {
                Job job;
                lock.lock();
                try {
                    while (null == (job = queue.pollFirst())) {
                        if (!running.get()) break processing;

                        try {
                            jobAdded.await();
                        } catch (InterruptedException ie) {
                            ie.printStackTrace();
                        }
                    }
                    jobRemoved.signal();
                }
                finally {
                    lock.unlock();
                }
                job.run(dataSource);
            }

        }

        void start() {
            lock.lock();
            try {
                if (running.getAndSet(true)) return; // already running
                thread = new Thread(this, "Database worker");
                thread.start();
            }
            finally {
                lock.unlock();
            }
        }

        void stop() {
            Thread runningThread;
            lock.lock();
            try {
                if (!running.getAndSet(false)) return; // already stopped
                runningThread = thread;
                thread = null;
                jobAdded.signal();
            }
            finally {
                lock.unlock();
            }

            // wait for the thread to finish while not holding a lock
            try {
                runningThread.join(2000); // we give it 2 seconds to empty its queue
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            runningThread.interrupt(); // we interrupt it just in case it hasn't finished yet
        }

        void queue(final Job job) {
            if (!running.get()) throw new IllegalStateException("Worker thread is not running");
            lock.lock();
            try {
                while (queue.size() >= MAX_QUEUE_SIZE) {
                    try {
                        jobRemoved.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.addLast(job);
                jobAdded.signal();
            }
            finally {
                lock.unlock();
            }
        }
    }

    private static interface Job {

        void run(StringBuilder dataSource);
    }

    private static class LogValue implements Job {

        final String myData;

        LogValue(final String data) {
            this.myData = data;
        }

        @Override
        public void run(final StringBuilder dataSource) {
            dataSource.append(this.myData);
        }
    }

}
Mitja Gustin
  • 1,723
  • 13
  • 17
0

To answer my question:

The object that is locked is the object that reference variable lock is referring to. In this case a ReentrantLock object.

An important thing to note:

The code above is potentially misguiding. Creating a new lock object in a method would be done by respective threads, and the respective thread will only lock that was created in it's stack. If you want to lock a particular instance's variables or methods, that instance should have its own lock object and only that same lock object should be used for synchronisation.

Refer this question for more info. Refer this documentation of the lock in question.

subject-q
  • 91
  • 3
  • 19