Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Synchronized Method vs ReentrantLock

Say I have a server with multiple threads that share a reference to a Data instance. Quick eg,

edit1: Updated for readability

public void main() {
 Data data = new Data();
 ReentrantLock rl = new ReentrantLock(true);
 ReadThread t1 = new ReadThread(data, rl);
 UploadThread t2 = new UploadThread(data, rl);
 t1.start(); t2.start();
}

class ReadThread extends Thread {
private Data data;
private ReentrantLock lock;

ReadThread(Data d, ReentrantLock l){ data = d; lock = l; }

     void run(){
        lock.lock();
        try{
        data.put(aString)
        } finally { lock.unlock(); }
     }
}

class UploadThread extends Thread {
private Data data;
private ReentrantLock lock;

UploadThread(Data d, ReentrantLock l){ data = d; lock = l; }

     void run(){
        lock.lock();
        try{
        data.put(aString)
        } finally { lock.unlock(); }
     }
}

Is it better to use locks like above, or synchronize the put method like below,

class Data {
 private LinkedList<String> data = new LinkedList<String>();
 synchronized void put(String x){ data.add(x); }
}

This is pretty rough,

I'm mostly just concerned about the concurrency.

With the synchronized method am I correct in assuming the synchronization would occur on the class' "Data" instance/object? So one UploadThread could call the put procedure/method and one ReadThread could do the same in parallel. However using the ReentrantLock example, only one thread would be able to execute a put call at any one time?

What would happen if in the "Data" class I made the LinkedList static and I made the put method synchronized and static? Which is the best approach? Do I lose mut ex if I make things static?

like image 623
I2obiN Avatar asked Mar 10 '23 13:03

I2obiN


1 Answers

In Java synchronized sections are reentrant. This means that a single thread can enter the synchronized section as many times as required, but a new thread can only enter when no other threads are present. A thread currently within these sections has acquired a lock and will only return the lock upon leaving all synchronized sections. Apart from declaring synchronized through the method signature, synchronized can also be called on objects directly. For example; these two methods will have the same effect:

synchronized public void foo() {

}

public void foo() {
    synchronized(this) {

    }
}

The ReentrantLock is very similar to synchronized in that only one thread can acquire the lock at one time. If a thread reaches a lock.lock() statement it will wait until the lock is unlocked by another thread. If the thread already has the lock it will continue. This can be useful in more complicated situations where a single synchronized code block isn't enough.

What would happen if ... I made the put method synchronized and static?

If the method is static synchronized that means you are locking the class itself and not the instance of the class. It is locked independently of an instance synchronized method.


For your code:

The easiest thing to do here would be to make the Data object into a thread safe object. If you are not able to edit the code for this class, then one valid strategy would be to wrap the object in a thread safe wrapper.

interface Foo {
    void bar();
} 
class UnsafeFoo implements Foo {
    @Override bar() { ... }
}
class ThreadSafeFoo implements Foo {
    Foo foo;
    ThreadSafeFoo(Foo foo) { this.foo = foo; } 
    @Override synchronized bar() { foo.bar(); }
}

Foo unsafe = new UnsafeFoo();
Foo safe = new ThreadSafeFoo(unsafe);
like image 186
flakes Avatar answered Mar 20 '23 21:03

flakes