aspose file tools*
The moose likes Developer Certification (SCJD/OCMJD) and the fly likes Locking Suggestions and Help Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login


Win a copy of Spring in Action this week in the Spring forum!
JavaRanch » Java Forums » Certification » Developer Certification (SCJD/OCMJD)
Bookmark "Locking Suggestions and Help" Watch "Locking Suggestions and Help" New topic
Author

Locking Suggestions and Help

Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
I recently picked up a book Patterns in Java Volume 1 that contains a ReadWriteLock Pattern. I originally thought that the locking of this project would take the longest amount of time both in design and implementation. However, using the example program in the book I was able to finish the locking in 1 hour. Basically, the pattern states that there are two different locks: ReadLock and WriteLock. Any number of objects can grab a read lock and use it because they are only accessing data. The only stipulation is that if there are any write locks than everyone else has to wait. This is so that no one corrupts the data.
The main reason that I like this pattern is that it separates almost completely the LockingManager from the objects that use it. This is excellent for reuse!
The main drawback is that if any client fails before releasing a lock -- the only way to release that lock is to start over the application. Though this is a pretty major implication. I don't think that it is a show stopper for this project. The project indicates that we don't have to make this production level code. I am pretty sure that as long as I indicate that I am aware of this problem in the design decisions that I'll be ok.
Nevertheless, I am still going to see if I can modify the pattern a little bit so that it can keep track of lost clients.
What does everyone think?
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
While read and write locks are a good idea, the API as has been given to you does not allow you to distinguish between them. The instructions don't ask for it either. Admittedly every real database has them, but then again, building a real database is beyond the scope of the assignment
Separating out the lock manager is another good idea; nothing stops you from doing that even if you just implement exclusive locks.
Finally, the ability to clean up after dead clients is, I believe, also quite orthogonal to the type of locks you implement. In any case I don't think it's a requirement (although I personally did implement it).
- Peter
Gennady Shapiro
Ranch Hand

Joined: Sep 25, 2001
Posts: 196
I just looked up this design pattern and I like the fact that an incoming request doesn't have identify itself by name or another unique id. This makes it easy to manage lock requests from anonymous users.
I do have a few questions though.
This design pattern seems to be concerned with keeping count of read/write-threads for AN OBJECT. In other words, the way it is being described a Lock Manager keeps track of a singular object, in our case the Data, not a record. To keep track of N records you either have to instantiate N Lock Managers or modify the algorithm to have maintain 2 Vectors for waiting and outstanding reads and Vector of Vectors for waiting writes; and, of course, you have to alter your logic accordingly. Now this is getting a little too complex, no? Or is that OK to to issue a Lock Manager for every record?
Then there is a requirement to be able to lock the entire table. This presents another problem. Locking the whole table is "all-or-nothing" operation. That is, we either lock all the records or fail all together. Now, when such request comes you start locking records one at a time , then you realize that some record is already locked and you have to "rollback". At this point we need a "second-level" lock, a lock on lock if you will, i dont think simple synchronization will help here.
Also, how about modifying this pattern and just ignore the read-locks? It's OK with the requirements and makes it easier and faster.
Do I make sense? What do you think?

[This message has been edited by Gennady Shapiro (edited October 10, 2001).]
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
Originally posted by Gennady Shapiro:
I just looked up this design pattern and I like the fact that an incoming request doesn't have identify itself by name or another unique id. This makes it easy to manage lock requests from anonymous users.
But are you still able to fulfil the requirement that an attempt to write-lock a record twice should return without effect? Probably not.
This design pattern seems to be concerned with keeping count of read/write-threads for AN OBJECT. [...] Or is that OK to to issue a Lock Manager for every record?
The lock manager is commonly implemented using a Map which maps the record number to something identifying the client. You could use a Map mapping the record number to a lock descriptor object, if you wanted. That doesn't sound too bad.
Then there is a requirement to be able to lock the entire table. This presents another problem. Locking the whole table is "all-or-nothing" operation. That is, we either lock all the records or fail all together. Now, when such request comes you start locking records one at a time , then you realize that some record is already locked and you have to "rollback".
Agreed about "all-or-nothing". The implementation can be far simpler than that however. If you impose the restriction that a client single-threads its database access (not an outlandish requirement; JDBC does it too), then all you have to do is count how many locks are outstanding for that client, wait() until the total number of locks is equal that, then impose the database lock because you know all outstanding locks are yours.
At this point we need a "second-level" lock, a lock on lock if you will, i dont think simple synchronization will help here.
Synchronization and the wait()... notify() mechanism will do fine to handle "locking the lock". You do need a second-level lock in the sense of a separate database lock, as it is quite a different concept from simply locking all records. For instance, when all records are locked another client should still be able to add a new record. When the database is locked it shouldn't.
- Peter

[This message has been edited by Peter den Haan (edited October 11, 2001).]
Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
Good points. I think that I will have to modify the LockManager class so that it is more appropriate for the assignment. I think it is still a good idea to delegate the responsibility of locking to a separate class.
Thanks for the comments.
Gennady Shapiro
Ranch Hand

Joined: Sep 25, 2001
Posts: 196
Peter,
if I understand you correctly, and I think I do, you do suggest to implement a table-wide lock and in order to do that to run through the Lock Map (locks for all records) and block untill they all are released , right?
The only implication is that every time you attempt to lock a record you have to wait () until ( noMoreRecordLocks && noMoreTableLocks). Or is it better to have client wait() for noMoreRecordLocks and your recordLock wait() for noMoreTableLocks. The second approach seems more polymorphic.
Have we covered locking or have we missed something?
Thanks
[This message has been edited by Gennady Shapiro (edited October 11, 2001).]
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
Originally posted by Gennady Shapiro:
Or is it better to have client wait() for noMoreRecordLocks and your recordLock wait() for noMoreTableLocks. The second approach seems more polymorphic.
Mmmm, a layered lock manager where the record lock manager is built on top of the table lock manager? But there is a high degree of coupling between the record locks and the table lock - I'd like to see how you factor these two aspects out. If you can't, you'll end up with two lock manager classes that are so tightly coupled that keeping them separate doesn't make a lot of sense.
Or am I misunderstanding you?
- Peter
Gennady Shapiro
Ranch Hand

Joined: Sep 25, 2001
Posts: 196
Peter,
what I am thinking is this.
Database and Record locks are performed my the same class.
Just for the sake of terminology : for a record you have Write/Read locks, and the database you have Shared/Exclusive locks.
Before your lock() can aquire Write locks on records it must aquire at least Shared lock on the lock Map (database).
It's just a two-level access imlemented with 100% code reuse.
By the way, the spec explicitely calls only for write locks. Did you add your own readLock(), readUnlock()? Because if there are no read locks you cannot protect agaist dirty reads.
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
Sounds good, but a bit... overenthusiastic... for the assignment. Keep in mind that beyond a certain point, complexity will count against you - I suggest you stick with the XP mantra that you do the simplest thing that can possibly work (but no simpler).
The assignment only calls for write locks. The FBN application doesn't care about dirty reads - with just one table, there isn't much of an issue there anyway. You obviously have to assume that a read done outside a write lock is instantly stale - when booking a seat you'll re-read the record before modifying it.
- Peter
Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
I completely agree with Peter. I just ran in to trouble of originaly making it too simple with using a straight, unmodified design pattern. It really is not that dificult if you just create a LockManager that handles only what the requirements want. I was able to finish with it today - though I still have to do some testing on it.
Hey Peter, in your design did you account for a lost client connection when a lock was still set? I was thinking of having a timeout value for a lock. Any comments?
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
Originally posted by Terry McKee:
Hey Peter, in your design did you account for a lost client connection when a lock was still set? I was thinking of having a timeout value for a lock. Any comments?
I did account for it, but mainly because it was neat and easy to do, not because I thought it was a requirement. In my design, I handed out a separate database connection to each client, so I could use the java.rmi.server.Unreferenced interface to implement cleanup in three lines of code (along with the fact that it was the simplest design that would handle client identification without changing the signature of the lock methods, this was what convinced me the design was far superior to anything else I had managed to come up with: if the JFC is working with you rather than against you, you must be doing something right).
- Peter

[This message has been edited by Peter den Haan (edited October 14, 2001).]
Gennady Shapiro
Ranch Hand

Joined: Sep 25, 2001
Posts: 196
OK. I just implemented only Write lock/unlock and it fit into 20 lines of code. Basically, I just took the ReadWriteLock pattern and took the Read part out. Looks simple, maybe too simple.
Here's my problem though.
1. Before I have argued for DB (not client) to issue lock/unlock commands. But then I realized that they do want client to handle this. OK, so here's a situation: your client issues lock-read-write-unlock. Now imagine another client comes in between read-write and forgets to use locks. That makes the second client completely ignorant of our locking scheme and it ignores the record lock and may/will corrupt the data. In this archtecture there is no protection against malicious clients.
2. Question. When your RMI connection breaks and your server attempts to clean up its locks the server must attempt to unlock ALL locks because it does not really know which record(s) your connection actually locked. Correct?
Thanks
P.S. I just hooked locks to my SafeData...and its another 20 lines of code.
[This message has been edited by Gennady Shapiro (edited October 14, 2001).]
Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
As to your first question,
<code>What if the client forgets to use locks</code>
That will never happen if you build the client correctly. It is a programming issue of the client rather than a user issue. (By the way, I am going to implement the lock / unlock methods on the client, but they are just going to call the server methods.)
Your second question about breaking a connection. I think this is important, though as Peter mentioned, it isn't part of the requirements. I am planning on having a timestamp value associated with a lock. At startup time of the LockManager the timeout value can be specified OR the default timeout value of 20 seconds can be used. A separate thread will see if the time elapsed is greater than the timeout value. If it is then release the lock.
Now I haven't implemented this scheme yet so I am still working through some things. One of the major issues is that the client may truly continue to need the lock for a long period of time, though unlikely. In this case, it would be nice to keep a reference to the client so that I could throw an exception so they know what happened. Nevertheless, this doesn't have to be 'production' level code so I probably will just release the lock. Either way I will make note of it in my design decisions.
John Lee
Ranch Hand

Joined: Aug 05, 2001
Posts: 2545
Originally posted by Terry McKee:
As to your first question,
<code>What if the client forgets to use locks</code>
That will never happen if you build the client correctly. It is a programming issue of the client rather than a user issue. (By the way, I am going to implement the lock / unlock methods on the client, but they are just going to call the server methods.)

Right, user shouldn't be allowed to lock a record by themself, that is completely un-user-friendly. I have already implemented lock/unlock in Data class, then provide it to client through RMI, just like any other methods in Data class. So client calls lock() right before it display the records.
Originally posted by Terry McKee:

Your second question about breaking a connection. I think this is important, though as Peter mentioned, it isn't part of the requirements. I am planning on having a timestamp value associated with a lock. At startup time of the LockManager the timeout value can be specified OR the default timeout value of 20 seconds can be used. A separate thread will see if the time elapsed is greater than the timeout value. If it is then release the lock.
Now I haven't implemented this scheme yet so I am still working through some things. One of the major issues is that the client may truly continue to need the lock for a long period of time, though unlikely. In this case, it would be nice to keep a reference to the client so that I could throw an exception so they know what happened. Nevertheless, this doesn't have to be 'production' level code so I probably will just release the lock. Either way I will make note of it in my design decisions.
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
Addressing a number of issues raised...
Before I have argued for DB (not client) to issue lock/unlock commands. But then I realized that they do want client to handle this.
They seem to imply this by two things. First, by requiring you to write a class that implements all of Data's methods for remote mode. Second, by asking you to build the database part for re-use, this means you cannot include application-specific logic in the database or the server core. This doesn't necessarily prevent you from executing such logic on the server, but you would really need to go out of your way to do this.
Now imagine another client comes in between read-write and forgets to use locks.
As Terry notes, it does not seem to be a requirement to cater for this. If you are of a pessimistic nature, you can enforce lock discipline, either by throwing an exception when an attempt at modification is made without lock, or by acquiring and releasing an implicit lock when the client doesn't have one. Personally I chose the latter approach as it was closer to the behaviour of a "real" database.
When your RMI connection breaks and your server attempts to clean up its locks the server must attempt to unlock ALL locks because it does not really know which record(s) your connection actually locked. Correct?
No. You must track client identity to satisfy the requirement that a client may lock() a record twice without ill effect. The identity tracking will also help clean up exactly those locks held by the now-defunct client.
[about lock cleanup] One of the major issues is that the client may truly continue to need the lock for a long period of time, though unlikely.
I'm starting to sound like a broken record but the Unreferenced mechanism can take care of that for you too - basically, the distributed garbage collector "pings" a client from time to time and does not garbage collect unless the client fails to respond for a (configurable) time. Hand-coding this is indisputably outside the scope of the assignment, but if you get it for free, hey...
About the location of locking code - I'm convinced that locking is pure server-only functionality, in local mode the methods can be empty.
- Peter

[This message has been edited by Peter den Haan (edited October 15, 2001).]
Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
According to the documentation,
the Unreferenced interface is used only when there are 0 clients attached to the server. Maybe I am just not seeing how to use it properly, but I don't see how this could be used in a manner that is helpful. As I see it there are two things that really are important to this design:

  1. The server MUST have the ability to identify clients.
  2. The server should be able to determine if a client is still connected.

  3. The reason for the first issue is the following line:
    If an attempt is made to unlock a record that has not been locked by THIS CONNECTION, then no action is to be taken.
    The second issue is not required by the assignment, but if the server must have a mechanism to identify the client...it is not difficult to set up a process for checking if the client is still connected.
    I think I finally have this worked out...
    The remote client when it connects to the server it receives a client id (int or long). The lock and unlock method are defined as
    <code>public void lock(int record)
    public void unlock(int record)</code>
    My design would call for a change in the argument that is passed:
    <code>public void lock(ClientObject obj)
    public void unlock(ClientObject obj)</code>
    The ClientObject would just have the client id and the row as variables.
    That takes care of the first issue.
    The second issue would just require the server to know a method that would check if the client is still out there. Perhaps,
    <code>public boolean isConnected()</code>
    This would mean that when the client is connecting some reference is kept of that client.
    Here is a summary:
    CLIENT implements CHECKABLE ->> has the public boolean isConnected() method.
    CLIENT connects to SERVER
    SERVER keeps reference to CHECKABLE interface
    SERVER also returns a client id to the CLIENT
Mark Spritzler
ranger
Sheriff

Joined: Feb 05, 2001
Posts: 17257
    
    6

When 0 clients are attached to the "Remote Object" is the key. You will have the one object bound to the registry "The Server", You have the server create a remote object for the client, and pass it back to the client. That client will be the only one accessing that object, So if the client shutsdown, or something bad happens to it, the remote object can use the unreferenced() method to clean up
Mark


Perfect World Programming, LLC - Two Laptop Bag - Tube Organizer
How to Ask Questions the Smart Way FAQ
Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
So what implements the Unreferenced interface - the server, or just some 'dummy' object? I am still a little confused about how to use the Unreferenced interface. Could someone give a small example?
Mark Spritzler
ranger
Sheriff

Joined: Feb 05, 2001
Posts: 17257
    
    6

I think where youa re getting confused is that We are talking about two different classes. There is one class, call it your ConnectionFactory, you instantiant the class an bind this object to the registry.
Your client gets the class from the registry. It calls a method to return an instance of a different class, call it Connection.
This Connection Class is also a Remote Object, and therefore, each client gets their own instance, and this instance is kept on the server. This is the class that uses the unreferenced() method.
Does that make more sense, without giving away too much?
Mark
Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
What I am getting confused about is this I only have the DataServer registering with the registry. If I follow you correctly then each Connection itself would be bound to the registry. I am just not sure about this.
As I investigate more about the Unreferenced Interface a few things caught my eye. Depending on the type of network in place the actual implementation of determining whether a client is still connected may not work if bridges, routers, firewalls, etc. are in place. Therefore, I don't think I am going to use it. I think I am just going to establish a session id for each client. The server will also keep a reference of the client interface which will allow for determing if the client is still connected. I am almost done with this implementation which should take care of the requirements for the assignment.
Though I probably won't use it - I really would like to understand the Unreferenced interface better. Maybe someone could give me example that doesn't have to do directly with the assignment at hand.
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
Originally posted by Terry McKee:
What I am getting confused about is this I only have the DataServer registering with the registry. If I follow you correctly then each Connection itself would be bound to the registry. I am just not sure about this.
No, only the ConnectionFactory would be bound to the registry. While the Connection objects it creates are Remote, they are not registered. To the contrary, the "trick" is that each client gets its own Connection that is inaccessible to other clients. See Applying the Factory pattern to RMI. The Connection is also the natural place to implement the remote version of Data you're being asked to do, so it is not just a dummy object - to the contrary.
If you have problems getting your head around this, remember that it's the way every real database works. You use the JDBC driver to get connection objects. In fact, in theory a Java database could use RMI to implement its JDBC driver in exactly the way described here.
As I investigate more about the Unreferenced Interface a few things caught my eye. Depending on the type of network in place the actual implementation of determining whether a client is still connected may not work if bridges, routers, firewalls, etc. are in place. Therefore, I don't think I am going to use it.
Are there any situations where Unreferenced won't work, but RMI will? Only if the answer is "yes" this makes sense. But even so this is going to be an intranet type application, where you typically have good control over the environment.
I think I am just going to establish a session id for each client.
The question of whether to use a session ID or a Connection object for identification is mostly orthogonal to the question of whether to use a timeout or Unreferenced for cleanup...
Though I probably won't use it - I really would like to understand the Unreferenced interface better. Maybe someone could give me example that doesn't have to do directly with the assignment at hand.
Say a server - any server - registers a FoobarFactory. Using this factory, one or more clients can create a Foobar object that lives on the server; whether each client gets its own Foobar or whether it is shared is immaterial. You will probably want to clean up the Foobar, and the resources held by it, when all clients using it have died or no longer hold on to it. You can achieve this by simply implementing the Unreferenced interface; the RMI server will call this interface as soon as it thinks all references to the Foobar object have been lost.
It is much like a network-enabled finalize(), except that you get more guarantees when unreferenced() will be called. An important difference is that unreferenced() merely means that no remote references exist, they may still exist locally and the object is not necessarily eligible for "real" garbage collection.
- Peter

[This message has been edited by Peter den Haan (edited October 18, 2001).]
Patrick Wang
Greenhorn

Joined: Oct 15, 2001
Posts: 20
Hi Peter:
If Connection is unique for each client, assume there is no
connection pooling mechanism, do you think it is scalable?
Assume 2 million user concurrently connect to the server at one
time?
Thx
Pat
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
Originally posted by Patrick Wang:
If Connection is unique for each client, assume there is no
connection pooling mechanism, do you think it is scalable?
Assume 2 million user concurrently connect to the server at one
time?
I assume this is an academic question - the database as it stands would groan under 200 users let alone 2 million. But the answer is that it should be pretty scalable. The only state the object has is the link (possibly implicit when realised as an inner class) to a parent class that contains the lock manager and Data, plus what UnicastRemoteObject and RMI add to this. I cannot quantify it but I would be surprised if you wouldn't run into lots of other problems well before you ran into a remote object count limit.
- Peter
Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
I think I finally understand how to use the Unreferenced Interface. I started changing some of my design around and I was able to get rid of a lot of redundant classes and interfaces. Though still working out a few kinks, I think I am headed in the right direction. The main problem that I am dealing with is choosing the collection to use for the storing of locks. Originally I had a vector just putting in an object that has the row and the client id. But what if the client wants to hold more than 1 lock at a time? Though not that big of a deal if I just ignore that fact I was thinking that rather than assigning a client id for each client...assign a lock id every time the client needs to lock something. That way the client can hold as many locks as it wants.
Also,
I changed the lock and unlock signatures to this:
public void lock(int row, long lockID)
public void unlock(long lockID)
The lock method needs a specific lockID that will associate the correct client with the lock. The unlock method only needs to know the lockID to unlock since every lock has its own lockID.
Thoughts?
By the way, thanks for all of the comments so far...they have really helped.
Mark Spritzler
ranger
Sheriff

Joined: Feb 05, 2001
Posts: 17257
    
    6

Yes you are getting closer. But you don't even have to change the signature of the lock or unlock methods. As a matter of fact your data class only needs to store all locks, and doesn't need to know the client.
What do I mean by this. What if the Connection object, each user has, keeps track of it's own locks, and if it doesn't have the lock in their list, then it can't call unlock in the Data class.
Try using a HashSet. It has the best of both worlds of a Vector and a Hashtable.
Mark
p.s. Some of my answers might not show the entire answer, because I had the same problems, and By looking through the posts I got the answers, but not exactly, I still had to figure it out on my own a little to understand. I liked that because it still made me think and learn, without someone else writing it for me.
Terry McKee
Ranch Hand

Joined: Sep 29, 2000
Posts: 173
I think I understand what you are saying...if a client calls unlock that you are guaranteed (if you program correctly) that the client holds the lock. The only problem that I see is this:
The <code>unlock</code> method simply removes the lock from the specified record. If an attempt is made to unlock a record that has not been locked by this connection, then no actionis to be taken.
That means to me that I have to pass some kind of client identifier or lock identifier with the lock and unlock statements. I don't think it is the end of the world if I didn't provide this functionality, but it makes me nervous to not account for it.
Mark Spritzler
ranger
Sheriff

Joined: Feb 05, 2001
Posts: 17257
    
    6

Hmm, I wish I could draw it out. here it goes
.............Connection Factory Object
................|...........|...........|
..............Conn.....Conn.....Conn
...............Obj........Obj.......Obj
................|...........|...........|
..............Client....Client... .Client
................1...........2...........3
There are three clients each client has it's own Connection Object. therefore the Connection Object is the clientID, so to speak. Each connection Object really handles the locking and unlocking for the client.
When you create a connection object you pass a reference to the Data object, The data object only needs to keep track of all the locks, without even needing to know who locked it.
That's about as far as I can go without thinking that I am giving away too much. Even though I feel I gave too much away.
Mark

[This message has been edited by Mark Spritzler (edited October 19, 2001).]
[This message has been edited by Mark Spritzler (edited October 19, 2001).]
[This message has been edited by Mark Spritzler (edited October 19, 2001).]
[This message has been edited by Mark Spritzler (edited October 19, 2001).]
Peter den Haan
author
Ranch Hand

Joined: Apr 20, 2000
Posts: 3252
You could use a Map that maps a record number to an object identifying the client holding the lock. This will accomodate an arbitrary number of locks per client, and the Map's size scales only with the number of locks held (rather than the number of records in the database as in some alternative approaches).
- Peter
 
 
subject: Locking Suggestions and Help