Link to home
Start Free TrialLog in
Avatar of Javin007
Javin007Flag for United States of America

asked on

Best practices for thread safety in Java?

I've been trying to figure out the best way to go about locking threads while trying to "do stuff."  One of the problems I've come across is that a thread (or threads) may need an extended "lock" while another thread (or threads) are doing their jobs.  As far as I can tell using "synchronized" will only lock a thread for as long as you're within that specific method, and there's no way to span "locks" across methods.  Perhaps I'm just flat doing it wrong, but here's the scenario:  

4 Methods, A, B, C, and D.  

Thread 1 needs to call A, then D making sure B is not called in the meantime.
Thread 2 needs to call C, but needs to make sure that thread 1 is done with at least A, and that A is not run at all while C is running.
Thread 3 needs to call B.  
Thread 4 needs to call D, but needs to make sure Thread 2 has called C and Thread 1 and Thread 3 are not running at all during the process.  

It was making my brain melt, so I wondered if creating a "synchronizer" object that can be passed around from method to method, and possibly even across threads, was a horrible idea?  (In my mind, it seems like it would be.)  

The code I came up with for the "Synchronizer" class is as follows, and thus far I've yet to get a crash or lockup with it, but I believe that's just a fluke (or the threads aren't doing what I believe them to be doing).  

//Theoretically, this should allow objects to be synchronized across methods and threads instead of just within a single method.
//In practice, this will probably just cause a ton of race conditions and problems.
public class Synchronizer {

	//A custom object is needed to sync on.
	private class SyncLockObject {}
	SyncLockObject Lock = new SyncLockObject();
	
	//Track the synchronization of the object manually.
	boolean booLocked = false;
	
	//This is my attempt at a public locking mechanism that will (theoretically) allow thread safety across methods.
	public void lock() {
		//So if they want to turn the lock "on" first, do the system sync.
		synchronized (Lock) {
			//Now, if the thread is this far, but the boolean shows that the sync is already "on" have the thread wait until it's available.
			while (booLocked) {
				//Allow other threads to run if needed.
				Thread.yield();
			}
			//Theoretically, if we're this far, only one thread should have been permitted to get through the system's "synchronized" function.  So flipping the "locked"
			//to "true" then stepping out of the sync should allow any competing threads to get through one at a time and wait for the boolean to be flipped to "false" again.
			booLocked = true;
		} 
	}
	
	public void unLock() {
		//Simply flip the switch to unlock.
		booLocked = false;
	}
	
}

Open in new window




Maybe a more simple concept:  

I have a custom "LinkedList" object.

It's easy enough to synchronize when an object is added or removed from the list.  But what if another thread wants to lock the LinkedList, find an object, make some changes, then pass that object to another thread, allow it to make some changes, and only THEN unlock the list to allow for further additions or removals?  

I may just be doing everything wrong.
Avatar of krakatoa
krakatoa
Flag of United Kingdom of Great Britain and Northern Ireland image

Take a look around the java.util.concurrent API.
Avatar of dpearson
dpearson

You don't need to write your own Synchronizer class (writing your own custom locking code is really hard).

I think you can happily use the existing "synchronize" operation - you just need to control the scope of the object used to control the locking.

If your 4 methods (A,B,C,D) are all in the same class, then you can just use synchronized on all of the methods:

public class myClass {
   public synchronized A() { }
   public synchronized B() { }
   public synchronized C() { }
   public synchronized D() { }
}

The lock you get from this code is actually shared among all of the methods.
Internally this code actually is the same as this code:

   public A() {
       synchronized(this) {
            ... Your code
       }
   }

The key is understanding what "synchronized(this)" means.  Synchronized creates a lock on a specific object - not on a method.  In this case the object is "this" - i.e. the instance of the class that contains the methods A,B,C,D.  So if 1 thread is accessing method A, then another thread cannot access any of A,B,C or D.  They are all blocked - because the lock is on the object that contains the methods - not on the methods.

But what if methods A,B,C and D aren't on the same class?  Then what?
In that case you can create a shared object that they use as a common lock.
E.g. You can do:

public class MyLock {
    public final static Object lock = new Object() ; // We just need an object to lock on
}

And then write the methods like this:

public A() {
    synchronized(MyLock.lock) {
         // Your code
    }
}

public B() {
    synchronized(MyLock.lock) {
         // Your code
    }
}

Now method A and method B can't be accessed by 2 threads at the same time, because they each establish a lock on a shared, static object.  If one thread gets that lock, all other threads trying to get the lock will block and wait their turn.

Does that help?

Doug
Avatar of Javin007

ASKER

Doug:  It's kind of along the lines, but what I'm concerned about is "holding" a lock between methods, and even threads.

For example, you can't do "If blah, then Synchronize, otherwise blah."  I'm trying to think of the exact scenario that got me to this point, but I've been trying to read the greek that is "Concurrency" and my brain is muddled at the moment.  

Also, is it possible to see if an object is currently "locked" without actually locking it yourself?  Like some sort of "is locked" thing?  (Again, can't remember the reason I wanted that.)

Heck, at this point, I can't even remember why I wrote the code in the first place.
For example, you can't do "If blah, then Synchronize, otherwise blah."
Do you mean, you can't do this because of some aspect of the problem or Java doesn't allow this?
If it's the second, that's not really true.  You can control synchronize if you wish:

if (blah) {
    synchronized(this) {
          // Only lock if "blah" is true
    }
}

If it's some aspect of the problem, can you explain that further?

If you want to test to see if a lock is already being held, you can do that.  You need to use a slightly finer grained locking mechanism, like ReentrantLock.  It needs a little more care (you need to explicitly unlock it, while synchronize handles the locking and unlocking for you) but it gives you more control - including an "isLocked()" method.

More here:
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html

Doug
No, one of the things I'm looking for is along the lines of:  

If (A) Then Synchronize
If (B) Then Don't Synchronize
If (C) Then Don't Synchronize
If (D) Then Synchronize

*do stuff*

If Synchronized, then End Synchronize.

I only remember needing a solution to this a short while back, but now I can't think of a specific example as to why I'd need the ability.  But that was why the code (above) was originally written.  It did seem to make this functionality work, but I can't for the life of me remember why I needed it.
ASKER CERTIFIED SOLUTION
Avatar of dpearson
dpearson

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hrm. Certainly seems easy enough.  I'll have to come back if I ever figure out what the "need to" reason was.
Sounds good - we'll be here :)

Doug
Thread 1 needs to call A, then D making sure B is not called in the meantime.
This requirement (if I'm reading it correctly) looks like its the main one that can't be handled by synchronized statements/methods only, since they can only hold the lock for the duration of the method call, ie. there is no way to stop Thread 3 from running method B in between Thread 1's calls to A and D. For this you would need to use a Lock object (such as ReentrantLock) that specifically allows to be locked in one method (Method A) and unlocked in a different method (Method D). Then Method B also tries to obtain the lock at the start and unlocks it at the end, and you get the synchronization that you describe above.

If this point truly is a requirement that needs to be met, maybe you can raise a new question to discuss this in more detail.
McCarl:  I'll be looking at the ReentrantLock that you mention.  Could you peruse the code I posted, and see if this is a "sufficient" object to do this?