5
\$\begingroup\$

I'm looking for some input here. I have a class that contains a value which is updated every few seconds by a method within that class. Right now, access to this value across multiple threads is done via synchronization, which I would like to eliminate. Would this make sense?

class DataSegment {

    private MetricsUpdater metrics = new MetricsUpdater.getInstance();

    public String printValues() {
        StringBuilder sb = new StringBuilder();
        sb.append(value1);
        sb.append(morevalues);
        sb.append(metrics.myValue); // this is the value that's currently synchronized
        return sb.toString();
    }
}


class MetricsUpdater {

    private String myValueSynchronized;
    public String myValue;

    public static MetricsUpdater getInstance() {
        if (theInstance == null) {
            theInstance = new MetricsUpdater();
        }
        return theInstance;
    }

    // this runs on a timer but to keep it simple I'll just define the method...
    private void updateMetrics() {

        synchronized(myValue) {
            // also, to keep things simple, I've replaced all of the actual logic with a method called someMethodToUpdateMyValue()
            myValueSynchronized = someMethodToUpdateMyValue();
            myValue = myValueSynchronized;
        }
    }
}

There can be many instances of DataSegment all reading from myValue, but the metrics class is a singleton. myValue only updates every 5 seconds or so and only MetricsUpdater is allowed to write to it. Does that make sense?

Does it even need to be synchronized at all if all of the other threads are only allowed to read it?

\$\endgroup\$
1
  • \$\begingroup\$ Where is the theInstance declared? \$\endgroup\$
    – levi
    Commented Jul 18, 2014 at 22:27

2 Answers 2

3
\$\begingroup\$

Your code is not synchronized at all at the moment. There are two major mistakes:

  1. You are only synchronizing in the thread(s) which are writing the value, but are never synchronizing on the threads reading the value. You must always synchronized on all threads that read/write a value which is shared between thread. Absolutely nothing is synchronized if you only synchronize on read or only on write.

  2. You use synchronize(myValue), but you keep changing myValue, so you are synchronizing on different objects over time, which basically means no synchronization.

By the way, it is possible that you made some tests and everything seemed synchronized. However, you should be aware that those tests would fail on other cpu's, or could fail on your machine if you tried them enough time.

More general comments:

  • Your whole class MetricsUpdater can be dropped for a volatile String myValueSynchronized. Note however that this only work with references to immutable Objects (such as String), or raw types.

  • To answer your question "Does it even need to be synchronized at all if all of the other threads are only allowed to read it?"
    NO!
    You could forgo synchronization however if the value was final and unmodifiable.

\$\endgroup\$
2
\$\begingroup\$

Does it even need to be synchronized at all if all of the other threads are only allowed to read it?

Yes. It's commonly thought that thread safety relates only to read-write cycles, but the only assumption you can reliably make without synchronisation is that a thread can at least see its own changes; everything else is maybe-sorta-possibly at best and a politician's promise at worst.

Processor cores may--and often do--take copies of data from main memory to work on. Even if a thread does decide to write through to main memory, the other threads need to catch on that something has changed, and reload it when needed. Otherwise, the other cores will happily chug on with their local copy.

So access should still be controlled, though it doesn't need to be via the synchronized keyword. A field that is either written independently of its previous value, or written only by the same thread, can be safely guarded by volatile.

I'd say there are three main ways to accomplish what you want, in varying degrees of features and safety:

// [1] Synchronized, may suffer under heavy contention
class DataSegment {
  private Metrics metrics = Metrics.getInstance();

  public synchronized String printValues() {
    return value1 + morevalues + metrics.cache;
  }
}

class Metrics {
  // multiple fields
  String cache;
  Object other;
  int stuff;

  synchronized void updateMetrics() {
    // ...
  }
}

// [2] Locks, more complicated than 1, but shared read access
class DataSegment {
  private Metrics metrics = Metrics.getInstance();

  public String printValues() {
    metrics.lockForReading();
    try {
      return value1 + morevalues + metrics.cache;
    } finally {
      metrics.unlockForReading();
    }
  }
}

class Metrics {
  private final ReadWriteLock rwlock;

  String cache;
  Object other;
  int stuff;

  void lockForReading() {
    rwlock.readLock().lock();
  }

  void unlockForReading() {
    rwlock.readLock().unlock();
  }

  void updateMetrics() {
    rwlock.writeLock().lock();
    try {
      // ...
    } finally {
      rwlock.writeLock().unlock();
    }
  }
}

// [3] Bare minimum volatile
class DataSegment {
  private Metrics metrics = Metrics.getInstance();

  public String printValues() {
    return value1 + morevalues + metrics.cache;
  }
}

class Metrics {
  volatile String cache;

  void updateMetrics() {
    // ...
  }
}

Here's a quick rundown of the ways to synchronize, how to choose, and when to use them:

synchronized is the basic, easiest, least surprising way. It guarantees that any threads entering the block will see all changes threads made before leaving the block. Use it as your first recourse, when you have blocks of code that can not tolerate interference from other threads, such as when doing coordinated or non-atomic reads and writes. JVMs tend to be pretty smart with synchronized, causing little overhead under light contention.

Locks are more specialist tools, allowing finer control than synchronized does. Use locks when you need features that synchronized doesn't provide (like shared/exclusive locking), when your critical sections are not neatly confined to code blocks, or when you have heavy contention.

volatile marks a field as uncacheable, meaning all threads must access main memory to read or write. When a field is volatile, all reads from and writes to (and through) it count as memory barriers, just like an enter-and-leave of a synchronized block does. However, volatile is non-blocking, and threads can interfere with each other. Use volatile when reads and writes are uncoordinated and independent.

\$\endgroup\$
1
  • \$\begingroup\$ This was extremely helpful, thanks. I am working on the volatile approach. This is for an Android app and some of the threads which access this field are created by the UI thread. If they are blocked and waiting, even for a few milliseconds, it can negatively affect the user experience. Note: I didn't write the original code, I'm just trying to clean it up. :) \$\endgroup\$
    – AWT
    Commented Jul 23, 2014 at 15:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.