• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Tim Cooke
  • paul wheaton
  • Jeanne Boyarsky
  • Ron McLeod
Sheriffs:
  • Paul Clapham
  • Liutauras Vilda
  • Devaka Cooray
Saloon Keepers:
  • Tim Holloway
  • Roland Mueller
Bartenders:

NX: a little more locking don't do no harm...

 
Ranch Hand
Posts: 7729
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
In the NX (Constructors) assignment we have to implement a couple of methods

Once we have locked a record and obtained our lock cookie we may proceed to update a record or mark the record as deleted.
The lock cookie authorizes the client to be able to perform the update or delete operations; any other client supplying a different cookie will be rejected.
My question is: can the update and deletion operations be now left unsynchronized? I want to assume that the single authorized client will not perform any multithreaded update, delete, or unlock operations.
Any thoughts, anybody?
Thanks in advance
-Barry
 
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Well, I think you could assume that no other threads will use the same key, yes, if that's a helpful assumption. But you need to synchronize on something in order to check whether the cookie is valid - because that's shared data, needs to be protected, right? I sync on a small monitor object assiciated with a particular record, so I just retain the sync on that object until I'm done with the update. No one can inturrupt what I'm doing with that record, but other records may be accessed. If you're syncing at a higher level, e.g. on the single Data object, then there's more incentive to release that sync lock ASAP since all other threads need it too. You could check the cookie validity in synced code, then exit the sync to do the update, then later there will be a sync again to unlock the record (since again, that deals with the same shared data about cookie validity). Might be worthwhile if you don't want to have separate monitors for each record.
 
Barry Gaunt
Ranch Hand
Posts: 7729
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks Jim.
When I'm setting and clearing the cookie I need synchronization, I agree. I need synchronization on clearing the cookie so that a waiting client gets notified. But once the cookie is written and returned to the client, it's read-only data for the update and deletes. Identifying the client for the update is not modifying the cookie in any way whatsoever, so it does not have to be synchronized. So once the client has the cookie and she promises not do any fancy stuff (put in a contract), the updates and deletes are protected by the cookie.
Using this, together with NIO file channels being thread-safe, and using positioned writes (which do not modify the file pointer), it makes the file stuff easy. Any buffers used would have to be thread local though.
Or am I having visions...
-Barry
 
Ranch Hand
Posts: 2937
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Using this, together with NIO file channels being thread-safe, and using positioned writes (which do not modify the file pointer), it makes the file stuff easy.
The purpose of lock() and unlock() is not to ensure the database file integrity, but to implement the business requirement where the count of some field (remaining seats in FBN, and something equivalent in the other exam version) should not go below zero. Thus the sequence lock-read-modify-unlock.
So do not confuse lock/unlock with the synchronization of read/write from the database.
Eugene.
 
Ranch Hand
Posts: 64
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi All:
My assignment is the Room Reservation application, which has the Lock/Unlock methods. I think both lock/unlock and synchronization are required. The lock/unlock feature reserves a record for a user to use. When the user actually performs the operation, say a modify, any other user should not sneek in and read the record. If they did, they might read inconsistent fields. So even though my reads do not respect lock, they are synchronized since I want all fields to be consistent. All my modifies are synchronized, of course.
Thx
 
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Barry Gaunt:
In the NX (Constructors) assignment we have to implement a couple of methods

Once we have locked a record and obtained our lock cookie we may proceed to update a record or mark the record as deleted.
The lock cookie authorizes the client to be able to perform the update or delete operations; any other client supplying a different cookie will be rejected.
My question is: can the update and deletion operations be now left unsynchronized? I want to assume that the single authorized client will not perform any multithreaded update, delete, or unlock operations.
Any thoughts, anybody?
Thanks in advance
-Barry



Hi Barry,
I'm a big advocate of the rule of synchronization, which states: Don't synchronize unless you have to. Because a given record is locked, I don't think you need to synchronize on anything else in order to 'double check' it's integrety. Doing so is somewhat redunant, and really makes the lock/unlock superfical. Your system already provides a threadsafe locking mechanism, why would you need another?
JMO, not big deal.
M
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Identifying the client for the update is not modifying the cookie in any way whatsoever, so it does not have to be synchronized.
Mmmmm... I'm always a bit wary of logic like this. Just because something is read-only doesn't mean it doesn't need to be synchronized. Consider an example:

Here add() is synchronized since it modifies the count, but get() is not, since it's just reading the value. Sound good? There are a few problems:
(1) Because of the way the Java memory model works, it's possible for different threads here to cache their own local copies of the count field, and return those when get() is called. So the value returned by get() may be old - and different threads may experience different amounts of lag. If different threads invoke get() and then write the result to a common logger (which is synchronized) you may observe that a later log entry has a lower count value than an eariler log entry - even though the class is written to be always increase (assuming positive x and no overflow). This is the sort of subtle bug that usually won't matter to anyone, but will occasionally bite you in the ass, and be nearly impossible to recreate or analyze.
(2) Worse - what happens if count is incrementing from 0x0000FFFFL to 0x00010000L (or for that matter, crossing any multiple of 0x0001000L)? Atomicity of long reads is not guaranteed unless you synchronize (or use volatile, but it turns out this "guarantee" is worthless because most JVMs suck in this respect). So if you manage to get() while it's in the process of incrementing, you might read one byte of the old value, and one of the new value. Yielding either 0x00000000L or 0x0001FFFFL - both of which are way off from the actual value. This could lead to a much more severe error - a thread can observe a value which is not just a little bit old, but completely erroneous.
OK, so this is why I don't trust the idea that read-only code doesn't need to be synchronized. I mean, it's OK in the above example if you accept that the count value may be a little off, and on rare occasions it may be a lot off. For some applications, like a web counter, that's fine. No one cares about its accuracy that much. But for other applications, you could have serious problems with unsynchronized reads.
Now, let's go back to the lock / update / unlock scenario. Let's say that you're trying to update() something using an invalid cookie. In this case, if update() returns a completely wrong value, so what? The chances that it matches the cookie you're trying to use are only 1 in 2^64, same as for most any invalid cookie attempt. Impossible to avoid, and not worth worrying about. Fine. Now what if someone is trying to use a cookie that they've just unlock()ed? This actually might work, since the update() may read a slightly old value of the cookie, namely the one that was just unlock()ed and should no longer be valid. So there's a possible security hole here, though it's difficult to see offhand how this would cause much trouble unless it's being agressively exploited by a malicious cracker. Which may or may not be a concern depending on how this code eventually gets used.
Now - what about the common scenario of a user locking a record and then trying to update() it using a valid cookie? Here, we may have a real problem. The update() may fail because either (a) it's reading an old cookie value, or (b) it's reading a completely erroneous cookie value. In which case you get a SecurityException. Very annoying to the user, as there's no apparent reason for it. Maybe this doesn't happen very often - certainly, the completely erroneous read is probably not common. However the slightly-out-of sync cookie could be a frequently occurring problem on some machines. Especially if your code is written to do a lock() followed immediately by an update(). In which case users could be routinely cursing your name (assiming they wouldn't do this anyway).
[Max]: I'm a big advocate of the rule of synchronization, which states: Don't synchronize unless you have to.
I would emphasize the flip side of this: if you're at all uncertain whether synchronization is really necessary in a given situation, it's usually safer to synchronize. I'd usually rather debug an application which is slow but correct and consistent, than one that is fast but occasionally fails for no apparent reason*. Though of course the nature of the user requirements can influence this decision quite a bit either way.
The ideal, of course, is to have a fast app that is consistent. This depends on eliminating synchronization wherever you can guarantee it isn't needed, but using synchronization correctly whenever it is needed. Which admittedly can be tough to figure out...
[ May 31, 2003: Message edited by: Jim Yingst ]
 
Barry Gaunt
Ranch Hand
Posts: 7729
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thanks for the detailed reply, Jim, much food for thought there.
I'm quite amazed about volatile being unrealiably implemented by most JVMs because I was basing my idea on that. Is this still true of recent JVMs? 1.4 and up?
Also, is it expected that we, and the examiners, know that (unrealiable implementation by some JVMs)?
-Barry
PS. I've just taken a look at the Bug Database.
]Horror!!!
[ June 01, 2003: Message edited by: Barry Gaunt ]
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator



[Max]: I'm a big advocate of the rule of synchronization, which states: Don't synchronize unless you have to.

I would emphasize the flip side of this: if you're at all uncertain whether synchronization is really necessary in a given situation, it's usually safer to synchronize.


Hi Jim,
I have to disagree here, and I think the sentiment is consistent with Sun's, which migrated away from Vectors to ArrayLists, with the chief difference being, of course, that Vectors are synchronized, and ArrayLists are not. Also, code blocks are not synchronized unless you explicitly make them so: to me, that reflects the synchronization rule. OTOH, Sun migrated away from Enumeration to iterator, specifically for Thread safety( though not through synchronization), so there's a reasonable basis for disagreement here. However, the issue tipping factor, for me, is the code is not synchronized by default: thus, the Java language, by it's nature, seems to advocate synchronization on demand, and not in case.
Ok, but what does this have to do with two locks? Basically, I think two locks are overkill, and can open to door to deadlocks and other threading problems. Even if you implement them correctly, the code is still that much more risky and complicated to maintain, because the people coming after you have to know how to do it correctly also. All in all, I'm an advocate of single monitors, if @ all possible.
M
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
[BFG]: I'm quite amazed about volatile being unrealiably implemented by most JVMs because I was basing my idea on that. Is this still true of recent JVMs? 1.4 and up?
I'm not sure. Bil Pugh provides a set of JVM compliance tests for volatile. In particular, AtomicLong.java. This and other tests work fine for me using SDK 1.4.2 on Windows XP (failed for 1.3) - other users seem to get similar results. But I haven't seen any definitive statements that volatile is "fixed" now; I see too many bug reports still open on the list you found. I suspect they've patched the most egregious and widespread problems on Windows at least, but there may well be problems still out there. At this point, volatile has to earn my trust.
I note that many of the discussions I've found on this point to JSR 133 as where these issues will (hopefully? ultimately?) be resolved. The page for 133 claims it's intended to be delivered as part of Tiger - however I can't find any other recent info on this. Articles about Tiger talk about generics and related stuff; no mention about 133. So, I'll believe it when I see it.
Some past ranch discussions of volatile:
https://coderanch.com/t/369557/java/java/Volatile-modifier-dependable
https://coderanch.com/t/231723/threads/java/Behaviour-Volatile-variables
https://coderanch.com/t/232013/threads/java/threadsafe
Also, is it expected that we, and the examiners, know that (unrealiable implementation by some JVMs)?
Dunno really - I'd guess no, at this point, based on the fact that the problems seem fixed in recent JDKs. Sun might be happy if we pretend the language works as specified. :roll: And maybe it does now - but it certainly didn't.
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
[Max]: I have to disagree here
Them's fightin' words! Howzabout you'n me step outside and... err, waitaminnit. You're the one who teaches martial arts, right? You'll probably kick my butt. OK, I'll just verbally disagree. Somewhat. With respect.
and I think the sentiment is consistent with Sun's, which migrated away from Vectors to ArrayLists, with the chief difference being, of course, that Vectors are synchronized, and ArrayLists are not.
Well, to my mind the biggest problem with Vectors is that even if you do require synchronization, most of the time Vector's synchronization is at the wrong level. E.g. individual methods are synchronized, but you often need synchronization to prevent interruption between method calls as well. The most common example:

(I know we could use an Iterator above, but get(i) is still pretty common, and also faster.) The problem of course is if another thread does a remove() just before the get(), we may get a NoSuchElementException or something similar. Synchronization in this case (if required at all) needs to be handled by a block enclosing both the size() and the get(). This sort of thing happens fairly often, and the "thread safety" of Vector provides a false sense of security that does more harm than good, IMHO. That doesn't mean that synchronization per se should be avoided; it means that if you use synchronization, it should be put in at the right level.
It's true that Collections.synchronizedList() and similar methods allow us to put this false sense of security back into our programs, again, at the wrong level. But fortunately these methods are frequently overlooked by many developers, so they don't do as much damage.
Aside from the "false security" issue, there was a performance reason to move away from Vector's approach.
The move from Vector to ArrayList was because Sun wanted to make synchronization optional and more under the control of the programmer. By the time collections came along, there wasn't any practical way to add an "unsynchronized" keyword to the langauge which would somehow remove the synchronization that had already been put into Vector; they had to build a replacement from scratch. Much easier to put in synchronizeation when you need it, than to take it out (from a library class) when you don't.
A nested digression: speaking of the evil Vector class, why on earth do you (Max) use it in your Denny's DVDs solution? E.g. on pp. 128-131 of your book. The reservedDVDs variable would be much better off as a HashSet rather than Vector, no? (Especially if the database ever gets a larger number of records.) Is this a case where the assumption is that developers may not know their collections very well, and you didn't want to get into that discussion? I suppose I can understand that. But my own opionion is, collections have been around for some time, and SCPJ 1.4 expects programmers to understand them. It's past time to stop coddling people by treating Vector as the default collection - at this point it holds people back more than it helps them.
OK, done with that bit of venting. End nested digression, back to our main digression.
However, the issue tipping factor, for me, is the code is not synchronized by default: thus, the Java language, by it's nature, seems to advocate synchronization on demand, and not in case.
Hmmm, maybe. But personally I'm having a hard time imagining the converse, to find a workable way to set the language up so that synchronization was the default. What would they do, make all methods synchronized by default? To me, that would just magnify the problems I have with Vector and spread them throughout the entire language. And probably introduce all sorts of possible deadlock problems. I think the decision to make non-synchronized the default reflects the fact synchronization works best when implemnted in a carefully considered manner - putting it everywhere is just as bad as putting it nowhere.
Our positions may not be all that far apart; we may just be interpreting each other's arguments more extremely than they were intended. Your "synchronize only when you need to" would be more palatable to me if we add "but study carefully to be really sure you don't need to". Conversely my "when in doubt, synchronize" might be better with the addendum "but work hard to resolve your doubts, because unnecessary synchronization can just complicate things and bog them down, or create deadlock." Either way, analyzing how the code really works is critical to making a good decision here.
Ok, but what does this have to do with two locks? Basically, I think two locks are overkill, and can open to door to deadlocks and other threading problems. Even if you implement them correctly, the code is still that much more risky and complicated to maintain, because the people coming after you have to know how to do it correctly also. All in all, I'm an advocate of single monitors, if @ all possible
I agree with this sentiment in general, but I don't think it's possible to guarantee that the program will obey the API we're assigned to implement unless we synchronize on something in order to look up expected cookie values. Or maybe use volatile, if we decide to trust that. But latency in memory reads is too much of a risk, I think, if it prevents users from updating a record they've already unlocked.
Note that I'm not 100% sure I know what you mean when you refer to "two locks" - two synchronizations to write and then read a cookie value? The problem is that "lock" is used in the assignment to refer to whether a record is locked or not; this is independent of whether we're currently in synchronized code or not. I try to refer to synchronization and monitors when that's what I mean, and use "lock" only to refer to whether a record is considered locked. It's all Sun's fault for using the word "lock" for two different things here.
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Jim Yingst:
[Max]: I have to disagree here
Them's fightin' words! Howzabout you'n me step outside and... err, waitaminnit. You're the one who teaches martial arts, right? You'll probably kick my butt. OK, I'll just verbally disagree. Somewhat. With respect.


Don't worry, it's rare that a martial artist can actually fight their out of a wet paper sack. Besides, you're the sheriff: you could just take me out back and shoot me, I suppose


and I think the sentiment is consistent with Sun's, which migrated away from Vectors to ArrayLists, with the chief difference being, of course, that Vectors are synchronized, and ArrayLists are not.
Well, to my mind the biggest problem with Vectors is that even if you do require synchronization, most of the time Vector's synchronization is at the wrong level.


Yes, a lot of people misunderstand the synchronization in vectors. However, if you use it the right way, that is, as a working volatile, then it can be useful. For example, one of my private students is using a static Vector in the NX exam to store deleted record numbers: nothing wrong with that.


E.g. individual methods are synchronized, but you often need synchronization to prevent interruption between method calls as well. The most common example:

(I know we could use an Iterator above, but get(i) is still pretty common, and also faster.) The problem of course is if another thread does a remove() just before the get(), we may get a NoSuchElementException or something similar. Synchronization in this case (if required at all) needs to be handled by a block enclosing both the size() and the get(). This sort of thing happens fairly often, and the "thread safety" of Vector provides a false sense of security that does more harm than good, IMHO. That doesn't mean that synchronization per se should be avoided; it means that if you use synchronization, it should be put in at the right level.
It's true that Collections.synchronizedList() and similar methods allow us to put this false sense of security back into our programs, again, at the wrong level. But fortunately these methods are frequently overlooked by many developers, so they don't do as much damage.
Aside from the "false security" issue, there was a performance reason to move away from Vector's approach.
The move from Vector to ArrayList was because Sun wanted to make synchronization optional and more under the control of the programmer. By the time collections came along, there wasn't any practical way to add an "unsynchronized" keyword to the langauge which would somehow remove the synchronization that had already been put into Vector; they had to build a replacement from scratch. Much easier to put in synchronizeation when you need it, than to take it out (from a library class) when you don't.


Completely agree: I was just pointing out that there was a conscious move away from synchronization 'in case' towards synchronization 'on tap'. This, IMO, seems to confirm the "don't synchronize unless you know exactly why you're doing it' mantra.


A nested digression: speaking of the evil Vector class, why on earth do you (Max) use it in your Denny's DVDs solution? E.g. on pp. 128-131 of your book. The reservedDVDs variable would be much better off as a HashSet rather than Vector, no? (Especially if the database ever gets a larger number of records.) Is this a case where the assumption is that developers may not know their collections very well, and you didn't want to get into that discussion?


I really just wanted to bring up Vectors, so I could talk about synchronizing at the 'right' level, and how a Vector's synchronization wasn't sufficient. I think I've got a special section just on this topic, and I bring it up again in the FAQs of chapter 4. In the SCJD training classes I teach, this misunderstanding of Vectors is a recurring problem.


I suppose I can understand that. But my own opionion is, collections have been around for some time, and SCPJ 1.4 expects programmers to understand them. It's past time to stop coddling people by treating Vector as the default collection - at this point it holds people back more than it helps them.


I couldn't agree more. I think it's important to know your tools, as it were. Collections are as important to a Java developer as a saw is to carpenter.


OK, done with that bit of venting. End nested digression, back to our main digression.
However, the issue tipping factor, for me, is the code is not synchronized by default: thus, the Java language, by it's nature, seems to advocate synchronization on demand, and not in case.
Hmmm, maybe. But personally I'm having a hard time imagining the converse, to find a workable way to set the language up so that synchronization was the default.


Ever work with VB?


Our positions may not be all that far apart; we may just be interpreting each other's arguments more extremely than they were intended. Your "synchronize only when you need to" would be more palatable to me if we add "but study carefully to be really sure you don't need to". Conversely my "when in doubt, synchronize" might be better with the addendum "but work hard to resolve your doubts, because unnecessary synchronization can just complicate things and bog them down, or create deadlock." Either way, analyzing how the code really works is critical to making a good decision here.


Well put.


Ok, but what does this have to do with two locks? Basically, I think two locks are overkill, and can open to door to deadlocks and other threading problems. Even if you implement them correctly, the code is still that much more risky and complicated to maintain, because the people coming after you have to know how to do it correctly also. All in all, I'm an advocate of single monitors, if @ all possible
I agree with this sentiment in general, but I don't think it's possible to guarantee that the program will obey the API we're assigned to implement unless we synchronize on something in order to look up expected cookie values. Or maybe use volatile, if we decide to trust that. But latency in memory reads is too much of a risk, I think, if it prevents users from updating a record they've already unlocked.


Not sure I'm understanding this point. Can you draw out what you mean by the latency in memory reads? What would the scenario be?


Note that I'm not 100% sure I know what you mean when you refer to "two locks" - two synchronization to write and then read a cookie value? The problem is that "lock" is used in the assignment to refer to whether a record is locked or not; this is independent of whether we're currently in synchronized code or not. I try to refer to synchronization and monitors when that's what I mean, and use "lock" only to refer to whether a record is considered locked. It's all Sun's fault for using the word "lock" for two different things here.


I may be misinterpreting the goals of the assignment, but I think one was to illustrate how synchronization really works, by having us effectively provide a synchronization implementation. To my point, the lock /unlock methods don't really serve a purpose otherwise. The scjd program design would be much more intuitive if there were no lock /unlock mechanism, and the appropriate methods simply synchronized quietly in the appropriate places, much as you're suggesting. The point here, I think, is that Sun wanted the developers to write a lock /unlock that was effective a synchronization block enter/ exit, so that the developers would get an intuitive sense of what really going on under the covers. Thus, the developer is demonstrating two things: they know how to use synchronization, and they understand how it functions under the covers. That's what I mean by 'two' locks. You've already got a thread safe monitor in the recNo that's locked. Achieving a second monitor by synchronizing on something else really doesn't seem to serve a purpose, AFIK.
All best,
M
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
you could just take me out back and shoot me, I suppose
I'll keep that option in mind for any future disagreements.
For example, one of my private students is using a static Vector in the NX exam to store deleted record numbers: nothing wrong with that.
But Vectors are EEEVIL! OK yes, some uses may be acceptable. I just get so tired of seeing them misused that I'd prefer people didn't learn about them at all until after they already understand the other collections. And understand threads well too, preferably.
I was just pointing out that there was a conscious move away from synchronization 'in case' towards synchronization 'on tap'. This, IMO, seems to confirm the "don't synchronize unless you know exactly why you're doing it' mantra.
Well, I just see it as them making the default behavior the one that's easist to change if you want or need to. You can add synchronization if it's not present, but it's hard to take it away once it's there.
I really just wanted to bring up Vectors, so I could talk about synchronizing at the 'right' level, and how a Vector's synchronization wasn't sufficient.
Cool. I confess I didn't read the accompanying text that closely, because I didn't really have any questions about how threads worked (at least as far as I know.)
Ever work with VB?
Only a miniscule amount; nothing dealing with how threads are handled there.
Not sure I'm understanding this point. Can you draw out what you mean by the latency in memory reads? What would the scenario be?
I thought about this some more and realized I overlooked something that substantially reduces the chance of this being a problem. But here's the scenario: First, background on my own design: I'm assuming that each time you lock a record, you generate a new cookie value which will be required for anyone to do an update(). We don't want someone who has previously locked and unlocked the record (but remembers their cookie value) to be able to use the old cookie value to do another update() on the record while its locked by someone else. So, we generate a new random long and save it somewhere in memory. This value is what we expect to be used by any future update() (unless the recrod is unlocked and locked again, generating a new value). I keep a List in memory to store the cookie values of all records. (For unlocked records, it's irrelevant -but there's still a slot in memory for the info.) I don't keep the whole record in memory, unlike Denny's DVDs - instead, I read from the DB file on demand. But there's no place for the cookie in the DB file, so need a data structure for that much at least. So anyway, here's an example scenario:
Record 1 starts out unlocked, with cookie value 0;
User A locks record 1, gets cookie value 12345. This value is stored using synchronized code.
Now user A attempts an update() using cookie 12345. The update code needs to check the user-supplied cookie against the value it has in memory. IF the update is being made from a different thread than the one which had previously called lock(), then since the code here is not synchronized (and assuming we're not using volatile because it's unreliable), the current thread may manage to retrieve an older cached copy of the cookie, such as 0, rather than the new "correct" cookie value of 12345. Then the code will compare the user-supplied 12435 with the retrieved value 0, and throw a SecurityException because it looks like the cookie is invalid. Even though the user did, indeed, supply the correct cookie value.
Now the "IF" above signals the part that I now realize is dubious: how likely is it that method calls from user A will be occurring in two different threads? Quite possibly, user A will only be using one thread, and so when update() does its unsynchronized read of the cookie value, any thread-local cached copy it finds will be the same as the value that was previously set by this same thread when it called lock(). Probably.
So - will all method calls from a single user come in on the same thread, or not? If I use a socket-based solution and start a separate server thread for each socket connection (one thread per user), then yes. (If I use a Selector - well, it would get complicated; let's not get into that.) However, I'm leaning towards RMI for its apparent/alleged simplicity. (Actually I really want to force myself to understand it better, since I don't currently have a good grasp of it.) According to the RMI Spec Section 3.2:

A method dispatched by the RMI runtime to a remote object implementation may or may not execute in a separate thread. The RMI runtime makes no guarantees with respect to mapping remote object invocations to threads. Since remote method invocation on the same remote object may execute concurrently, a remote object implementation needs to make sure its implementation is thread-safe.


So, I don't know how often this actually happens in practice - but if we're using RMI, it's apparently possible that user A's update() is called from a different thread than the lock() and thus an unsynchronized (non-volatile) read might get an old cookie value, and the user gets a SecurityException for no apparent reason. That's the sort of thing I prefer to guarantee against - though for some applications occasional failures may be considered "acceptable risk" as long as they don't show up too often in testing.
I may be misinterpreting the goals of the assignment, but I think one was to illustrate how synchronization really works, by having us effectively provide a synchronization implementation. To my point, the lock /unlock methods don't really serve a purpose otherwise.
Not as written, they don't - agreed. If I also add a non-blocking tryLock(int recNo) method I can get some use out of them. Users need to be able to "book" a constractor for a customer, meaning that the customer's ID will be stored as part of that record - which is only allowed if no other customer has already booked the record.
(Aother aside - this seems an idiotic design, since it omits date info. If customer X wants contractor M to do some work this week, shouldn't customer Y still be able to request M's services for next week? But it seems we're stuck with these requirements.)
A possible use case as I see it:
1. User A gets list of contractors
2. User A selects one one contractor and presses "Book'em" button. (record is locked)
3. User A gets a dialog asking for customer ID (which may default to the last ID used, but the user can represent multiple customers, so they need to be able to specify customer ID here.)
4. User B meanwhile tries to book the same record - gets message, "sorry, record is currently unavailable" (it's locked).
5. User A hits "Submit" and the record is updated, booked as his (unlocking record).
6. User B tries again - now gets "sorry, that record is already booked." (It's unlocked, but the customer ID has been filled in by someone else.)
Step 4 is where the concept of locking is useful, if we have a non-blocking way of checking if a record is already locked. Or we can just force user B to wait until A completes the operation or cancels (both of which unlock the record), but that's... less than user-friendly. The importance of step 4 is not all that great here - we could modify the process to just wait until the final "Submit" before checking to see if any other users have managed to sneak their booking in in the time since we started the process. If they did, user sees "sorry, too late" message and, well, they only wasted a few keystrokes typing in the customer ID. This would be more important though if the user had been modifying more fields, or if the fields were particularly long. Imagine updating a bunch of data and then learning your changes were overwritten a second later by another user who had never even seen your changes. That, I think, is the sort of scenario the lock/unlock methods are supposed to help with. It makes more sense in the context of possible future expansions of program functionality, like letting a user edit the whole record. Right now their use is quite limited, I agree - but there is some use at least.
Now having said all that, the concept of cookies associated with locks seems quite pointless, unless we're protecting against the possibility of malicious crackers substituting their own client and trying to wreak havoc with it. And if that's the case, well, we probably need to institute quite a few other security procedures as well. :roll: So I'm not really sure what the cookies are for, but what the hell, I'll implement them.
That's what I mean by 'two' locks.
OK, thanks.
You've already got a thread safe monitor in the recNo that's locked. Achieving a second monitor by synchronizing on something else really doesn't seem to serve a purpose, AFIK.
I've been vague about what exactly we're locking on, because I can think of a few different designs and I'm not sure what different people are envisioning. But my previous arguments cover why I think there's a need for update() to synchonize on something in order to check the cookie, to prevent the latent read problem.
Well, enough of my blathering for now. (Yeah, I know, why didn't I say that earlier...) Thanks for your input.
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
/*


Now user A attempts an update() using cookie 12345. The update code needs to check the user-supplied cookie against the value it has in memory. IF the update is being made from a different thread than the one which had previously called lock(), then since the code here is not synchronized (and assuming we're not using volatile because it's unreliable), the current thread may manage to retrieve an older cached copy of the cookie, such as 0, rather than the new "correct" cookie value of 12345.


I don't see how this is a problem. Even if client A calls update from Thread #2, this is after thread #1 has finished and updated the Main Memory. Even if Thread #2 is unsynchronized, it can't possibly begin until thread # 1 is finished, because the client can't call it until thread #1 finished executing. And since Thread # 1 has updated Main Memory, then Thread #2 is forced into picking up the correct data.


Now the "IF" above signals the part that I now realize is dubious: how likely is it that method calls from user A will be occurring in two different threads?


Amazingly, I can get this to occur in testing with some regularity. The trick is to let some other client release their remote object. Eventually, it will get picked up by the pooler. However, per the above reasoning, I think you're ok even if it does.
Good discussion,
M
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I don't see how this is a problem. Even if client A calls update from Thread #2, this is after thread #1 has finished and updated the Main Memory. Even if Thread #2 is unsynchronized, it can't possibly begin until thread # 1 is finished, because the client can't call it until thread #1 finished executing. And since Thread # 1 has updated Main Memory, then Thread #2 is forced into picking up the correct data.
I'm with you right up to the last sentence. If thread 2 is not synchronized, it may not be forced to go to main memory; it may have another cached copy. This depends on where these different threads are coming from exactly. If the RMI implementation is using some sort of thread pool to field method invocations as needed, then thread 2 may have been previously used to handle other method calls related to the same record, in which case it might still have a cached copy of what it thinks is the cookie for that record. In which case no, it doesn't need to go to main memory to refresh the cookie.
So OK, I don't know much about how RMI implementations actually work here. Can the pooled threads retain any instance data about the objects they're servicing? Dunno; quite possibly they can't. Does the run() method of a pooled thread enter a synchronized block at any point before it services another remote invocation? That would be sufficient to flush its memory, I think. But such details are largely unknown to us, and the RMI Spec quoted above doesn't fill me with confidence in this respect. So I fall back on words of the Book of Joshua 195:8-12:

In summary, whenever multiple threads share mutable data, each thread that reads or writes the data must obtain a lock. Do not let the guarantee of atomic reads or writes deter you from performing proper synchronization. Without synchronizations, there is no guarantee as to which, if any, of a thread's changes will be observed by another thread.


[ June 01, 2003: Message edited by: Jim Yingst ]
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Jim Yingst:
I don't see how this is a problem. Even if client A calls update from Thread #2, this is after thread #1 has finished and updated the Main Memory. Even if Thread #2 is unsynchronized, it can't possibly begin until thread # 1 is finished, because the client can't call it until thread #1 finished executing. And since Thread # 1 has updated Main Memory, then Thread #2 is forced into picking up the correct data.
I'm with you right up to the last sentence. If thread 2 is not synchronized, it may not be forced to go to main memory; it may have another cached copy. This depends on where these different threads are coming from exactly. If the RMI implementation is using some sort of thread pool to field method invocations as needed, then thread 2 may have been previously used to handle other method calls related to the same record, in which case it might still have a cached copy of what it thinks is the cookie for that record. In which case no, it doesn't need to go to main memory to refresh the cookie.



Note to self: use 'book of Joshua' phrase at first opportunity
I'll see your book of Joshua, and raise you the new testament, per the Java threading spec, as presented by Saint Gosling, and more recently to boot. It say, in effect, that before a Thread can start working with a variable, it needs to copy that variable in from Main Memory. Further, it says that after it's done working with that variable, the thread must push it's changes out to main memory. Remember, everything is being driven by the clientThread here: Thread # 1 and Thread #2 are inside of it's scope. Regardless of synchronization or RMI, if Thread #1 finishes fiddling with a variable(say cookieNum), and then Thread #2 starts working with that variable, Thread #2 much pick up the value from Main Memory: it has no choice. That is, when Thread #2 starts driving, the first thing it does it get the value of cookieNum from Main Memory, and go to work. And we know that cookieNum is as Thread #1 left it, because the language spec demands that Thread #1 update it in Main Memory before terminating.
Of course, the language spec could be implemented incorrectly, as in the case of volitile. However, a mistake of this magnitude would destabailize the entire language, IMO. As a matter of fact, the reason that volitile's instability is tolerable is because there is a guaranteed workaround, per the above.



So OK, I don't know much about how RMI implementations actually work here. Can the pooled threads retain any instance data about the objects they're servicing? Dunno; quite possibly they can't. Does the run() method of a pooled thread enter a synchronized block at any point before it services another remote invocation? That would be sufficient to flush its memory, I think. But such details are largely unknown to us, and the RMI Spec quoted above doesn't fill me with confidence in this respect. So I fall back on words of the Book of Joshua 195:8-12:

[ June 01, 2003: Message edited by: Jim Yingst ]


So your concern, as I read it, is that RMI could be hanging on to copies of the variables in Main Memory from a previous calling thread, and that those copies could be out of data with what's currently in Main Memory? AFIK, this is possible, but I don't think it's legal, from a language spec point of view. If it were, then you really couldn't use RMI without synchronizing on the remote object, and I'm not aware of any such restriction.
All best,
M
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Note to self: use 'book of Joshua' phrase at first opportunity
Glad you liked it.
I'll see your book of Joshua, and raise you the new testament, per the Java threading spec, as presented by Saint Gosling, and more recently to boot.
So this would refer to the Java Language Specification, 2nd Edition? (Published 2000, while BoJ is 2001?) Chapter 17: More Than You Ever Wanted To Know About Threads And Should Have Known Better Than To Ask? Okey-doke...
It say, in effect, that before a Thread can start working with a variable, it needs to copy that variable in from Main Memory
The closest I can find to this is from 17.3

# After a thread is created, it must perform an assign or load action on a variable before performing a use or store action on that variable. (Less formally: a new thread starts with an empty working memory.)


This requires that at some point before a variable can be used, it has to be loaded from main memory. But it doesn't have to have been done recently - if the thread has been around a while (part of the thread pool) it may have done the load some time ago - no obligation to repeat it. If there were a lock or volatile, I can find the rules that would specifically require a fresh load - but without lock or volatile, it's not required.
Remember, everything is being driven by the clientThread here: Thread # 1 and Thread #2 are inside of it's scope.
I'm not sure what this means. Is clientThread a thread running on the client? And threads #1 and #2 are on the server, right? Not sure what "scope" means in this context. I agree that things happen in a certain order as far as the client is concerned, but without synchronization or volatile, that's not necessarily seen the same on the server.
Regardless of synchronization or RMI, if Thread #1 finishes fiddling with a variable(say cookieNum), and then Thread #2 starts working with that variable, Thread #2 much pick up the value from Main Memory: it has no choice. That is, when Thread #2 starts driving, the first thing it does it get the value of cookieNum from Main Memory, and go to work.
No, only if it has never previously loaded the value from main memory. If a load of the appropriate has occured some time in the past since the thread's creation, the JLS requirements here are met. Unless there's a particular line I'm missing, which is very possible.
And we know that cookieNum is as Thread #1 left it, because the language spec demands that Thread #1 update it in Main Memory before terminating.
How do we know thread 1 has actually terminated? Though in this discussion, thread 1's lock() was synchronized, so I agree that the correct cookie value is in main memory at this point. I just don't think thread #2 is obligated to use it.
As a matter of fact, the reason that volitile's instability is tolerable is because there is a guaranteed workaround, per the above.
Agreed. The situation with volatile is vexing because it's a direct violation of specs - but it's always been possible to wrap accesses to a variable with sync locks, which achieves the same effect with slightly poorer performance. Volatile is of marginal use anyway; we can avoid it entirely for long and double, and be none the worse for wear. That's way Sun hasn't been compelled to fix it.
Of course when you say "per the above" I'm not sure you're talking about replacing volatile with synchronization, as I am. But I agree there is a workaround.
So your concern, as I read it, is that RMI could be hanging on to copies of the variables in Main Memory from a previous calling thread, and that those copies could be out of data with what's currently in Main Memory?
Yes.
AFIK, this is possible, but I don't think it's legal, from a language spec point of view.
I disagree as discussed above; but it's a pretty hairy spec, so maybe there's something I'm missing.
If it were, then you really couldn't use RMI without synchronizing on the remote object, and I'm not aware of any such restriction.
Or the remote object implementation could synchronize on something for every read or write of mutable shared data. That's how I interpret the RMI Spec I cited early on: "a remote object implementation needs to make sure its implementation is thread-safe". The only truly thread-safe ways to write or read mutable data are through synchronization or (maybe) volatile. IMO and all that. But I think Josh is on my side here, and Saint Gosling is curiously silent on some key points.
[ June 02, 2003: Message edited by: Jim Yingst ]
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
/*

Originally posted by Jim Yingst:
Note to self: use 'book of Joshua' phrase at first opportunity
Glad you liked it.


I really did, I had a smile on my face the whole time I was typing .



I'll see your book of Joshua, and raise you the new testament, per the Java threading spec, as presented by Saint Gosling, and more recently to boot.
So this would refer to the Java Language Specification, 2nd Edition? (Published 2000, while BoJ is 2001?) Chapter 17: More Than You Ever Wanted To Know About Threads And Should Have Known Better Than To Ask? Okey-doke...


You're right: BoJ was published first. however, we have to assume the spec is the final word, I think. And tangentially, the honeymooners preceded the flintstones, or so I hear.



It say, in effect, that before a Thread can start working with a variable, it needs to copy that variable in from Main Memory
The closest I can find to this is from 17.3

This requires that at some point before a variable can be used, it has to be loaded from main memory. But it doesn't have to have been done recently - if the thread has been around a while (part of the thread pool) it may have done the load some time ago - no obligation to repeat it. If there were a lock or volatile, I can find the rules that would specifically require a fresh load - but without lock or volatile, it's not required.



Almost, it's in the next bullet point down. That is, "After a variable is created, every thread must perform an assign or load action on that variable before performing a use or store action on that variable.".
When you create the cookieNum variable by calling Thread #1, that is,

Thread #2 must pick uo the new variable referenced by cookieNum, from MainMemory. Not the reference mind you, but the actual variable itself.



Remember, everything is being driven by the clientThread here: Thread # 1 and Thread #2 are inside of it's scope.
I'm not sure what this means. Is clientThread a thread running on the client? And threads #1 and #2 are on the server, right? Not sure what "scope" means in this context. I agree that things happen in a certain order as far as the client is concerned, but without synchronization or volatile, that's not necessarily seen the same on the server.


Sorry, I wasn't very clear here. What I mean is that the clientThread orders the server side requests, which may, or may not, spawn separate threads in RMI(our infamous Threads #1 and Threads #2). That is, the client's Thread makes sure that thread #1, which is synchronized, does a read->load->use->assign->store->write before it's done. Then, thread #2 is kicked of. Because thread #2 is being passed in a reference to cookieNum from main memory( which we agree - I think- was just updated by thread #1), then Thread #2 has no choice but to use the value of cookieNum from Main Memory. essentially, Because Thread #1 is synchronized, it must leave things in a stable state for Thread #2 in Main Memory. And because Thread #2 uses a new variable, it must look to Main Memory for that variable. It might be different if Thread #1 weren't synchronized, I dunno.

Regardless of synchronization or RMI, if Thread #1 finishes fiddling with a variable( say cookieNum), and then Thread #2 starts working with that variable, Thread #2 much pick up the value from Main Memory: it has no choice. That is, when Thread #2 starts driving, the first thing it does it get the value of cookieNum from Main Memory, and go to work.
No, only if it has never previously loaded the value from main memory. If a load of the appropriate has occurred some time in the past since the thread's creation, the JLS requirements here are met. Unless there's a particular line I'm missing, which is very possible.

[/QB]
Ah, I see what you're saying: Thread #2 could already have a reference to cookieNum, in which case, it would know that cookieNum had been updated. It could, if cookieNum weren't being passed in as a parameter and were globally available(I never though of that). However, it's not so in this case, because it is being passed in as a reference, and it's a new variable(abait an old reference). Am I being careful here, or did something get by me?


And we know that cookieNum is as Thread #1 left it, because the language spec demands that Thread #1 update it in Main Memory before terminating.
How do we know thread 1 has actually terminated? Though in this discussion, thread 1's lock() was synchronized, so I agree that the correct cookie value is in main memory at this point. I just don't think thread #2 is obligated to use it.


Thread #2 is obligated to use it, from my perspective, because it's a new variable, and the spec say that whenever a thread uses a new variable, that use must be preceeded by a read/load


As a matter of fact, the reason that volatile's instability is tolerable is because there is a guaranteed workaround, per the above.
Agreed. The situation with volatile is vexing because it's a direct violation of specs - but it's always been possible to wrap accesses to a variable with sync locks, which achieves the same effect with slightly poorer performance. Volatile is of marginal use anyway; we can avoid it entirely for long and double, and be none the worse for wear. That's way Sun hasn't been compelled to fix it.
Of course when you say "per the above" I'm not sure you're talking about replacing volatile with synchronization, as I am. But I agree there is a workaround.


I was talking about replacing volatile with synchronization, so we're on the same page here. As an aside, I wonder if volatile has been fixed in jdk 1.4?


So your concern, as I read it, is that RMI could be hanging on to copies of the variables in Main Memory from a previous calling thread, and that those copies could be out of data with what's currently in Main Memory?
Yes.
AFIK, this is possible, but I don't think it's legal, from a language spec point of view.
I disagree as discussed above; but it's a pretty hairy spec, so maybe there's something I'm missing.


Granted: I don't really feel like I 'know' it. It's just too darned ambiguous. Of course, IMO, Threading is the gateway to understanding complexity, so we're knocking on the right door at least .


If it were, then you really couldn't use RMI without synchronizing on the remote object, and I'm not aware of any such restriction.
Or the remote object implementation could synchronize on something for every read or write of mutable shared data. That's how I interpret the RMI Spec I cited early on: "a remote object implementation needs to make sure its implementation is thread-safe". The only truly thread-safe ways to write or read mutable data are through synchronization or (maybe) volatile. IMO and all that. But I think Josh is on my side here, and Saint Gosling is curiously silent on some key points.
[ June 02, 2003: Message edited by: Jim Yingst ]


Agreed, regarding the remote object implementation. But this goes beyond that, so the client's usage of that remote object. if the remote Object's weren't obligated to work with Main Memory before using a new variable, then Satan would win.
M
[ June 02, 2003: Message edited by: Max Habibi ]
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
You're right: BoJ was published first. however, we have to assume the spec is the final word, I think.
Agreed. (At least unless/until JSR 133 is resolved.) BoJ is interpretation and advice, with no authority as a spacification; if BoJ were to contradict the spec, BoJ would be wrong. However I'm talking about things which (as I see it) the spec is silent about, and BoJ offers a good interpretation of what that silence means.
[B]Almost, it's in the next bullet point down. That is, "After a variable is created, every thread must perform an assign or load action on that variable before performing a use or store action on that variable.".
When you create the cookieNum variable by calling Thread #1, that is,

Thread #2 must pick uo the new variable referenced by cookieNum, from MainMemory. Not the reference mind you, but the actual variable itself.[/B]
OK I agree that thread 2 will see a nice fresh copy of the data you pass as cookieNum. But what exactly does thread #2 do with it then? Rember we were talking about the update() method - update() is required to check the cookie it's given as an argument, to see if it's valid (equal to the cookie value previously set by the lock() method). That means it must have somehow, somewhere, saved the cookie value that was set by lock(). For simplicity let's say that the Data object used a long[] array for this:

It's accessing the expectedCookie data that's the problem. This is not a newly created variable - it's part of an object that will probably persist for the duration of the program. So the "after a variable is created" rule doesn't apply, save once at the very beginning. When expectedCookies[recNo] is accessed, a thread may see an old cached value of the data, rather than a fresh copy. Unless there's some sort of synchronization during the update(), as there was for the lock(). Any method that can throw SecruityException needs synchronization to check a client-supplied cookie value against a server-retained expected cookie value, and ensure an accurate result.
Now my own implementation doesn't actually use a long[] expectedCookies array - it uses an ArrayList of RecordMonitor objects, which contain an expectedCookie field. (Along with isLocked and isDeleted fields, and maybe other stuff for debugging.) I chose to leave most of the record info in the file, not memory (unlike Denny's DVDs) - but I needed at least the cookie values in memory, since they couldn't go in the file. But the basic arguments of the previous paragraph are the same - somehow, somewhere, there's an expected cookie value stored in an instance field (not a new variable) which we need to synchonize before we access - read or write.
Aside - looking at the assignment, we could technically define all cookies as 0 or whatever, and then cookie checking would become really simple, requiring no synchronization at all. But that's probably not what was intended. Tempting though...
Sorry, I wasn't very clear here. What I mean is that the clientThread orders the server side requests, which may, or may not, spawn separate threads in RMI(our infamous Threads #1 and Threads #2). That is, the client's Thread makes sure that thread #1, which is synchronized, does a read->load->use->assign->store->write before it's done. Then, thread #2 is kicked of. Because thread #2 is being passed in a reference to cookieNum from main memory( which we agree - I think- was just updated by thread #1), then Thread #2 has no choice but to use the value of cookieNum from Main Memory. essentially, Because Thread #1 is synchronized, it must leave things in a stable state for Thread #2 in Main Memory. And because Thread #2 uses a new variable, it must look to Main Memory for that variable. It might be different if Thread #1 weren't synchronized, I dunno.
Agree with everything except as noted above - thread 2 gets a fresh copy of cookie but not of expectedCookie, which is not a new variable (and thread 2 is not (necessarily) a new thread). Main memory does have the correct fresh data for expectedCookie at this point, thanks to thread 1's synchronization - but thread 2 may ignore it.
I was talking about replacing volatile with synchronization, so we're on the same page here. As an aside, I wonder if volatile has been fixed in jdk 1.4?
Maybe, sorta. See discussion above, just before "Them's fightin' words!" - which may have distracted you.
Agreed, regarding the remote object implementation. But this goes beyond that, so the client's usage of that remote object. if the remote Object's weren't obligated to work with Main Memory before using a new variable, then Satan would win.
I assume this means a client ends up refreshing from the server's main memory, not just the client's main memory, right? Sounds right to me (too tired to verify in the specs; I assume it's there somewhere). It's just the situation for the remote client implementation on the server that I'm concerned with.
Cheers,
J
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hey Jim,
Thought some more about this, and I think we're both right. Solution? synchronize the data modifying methods on the DbImpl class: That's how it's done in the previous assignment.
M
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Sounds reasonable. There's a slight catch though. For NX-Contractors, we're given an API which the Data class implements - in general it doesn't say that this class has to be thread-safe, only that the system has to guard against clients making concurrent access. So yes, this can be done at a different level, like in the Remote implementation. Except that the API for the Data's lock() method is written in such a way as to virtually require some synchronization inside lock(). ("If the specified record is already locked by a different client, the current thread gives up the CPU..." - this practically screams "use wait/notify" at us.) So once there's some sort of synchronization inside Data, it may be useful to put other synchronization in the same class, to keep in all in one place and hopefully make it easier to avoid nested locking. Or maybe not. Depends on whether we sync at record level or at Data instance level, or somewhere else. I recently talked about this here (though there are probably older discussions I missed; too lazy to search). Anyway, I don't think this prevents us from doing most synch in the Remote implementation (in fact for some designs it may favor it for all I know) but it's an extra complication to think about.
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I though that we has always agreed that you needed to synchronized inside of lock/unlock? It's only the modify that's in question.
M
 
Jim Yingst
Wanderer
Posts: 18671
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I though that we has always agreed that you needed to synchronized inside of lock/unlock? It's only the modify that's in question.
Right - well I wasn't sure you knew the exact requirements for NX-Contractors, so I was stating the relevant parts in case. But my main point in the last point was that, given that there's synchronization inside lock() and unlock(), it may be a good idea for other synchronization (e.g. update()) to be at the same level, in the same class, rather than somewhere further removed where it's harder to see how or if it interacts with the lock/unlock. My own original design had a number of different objects I sync on for various reasons, and while I inderstand how they interact I have to concede that it's a bit complicated for a junior programmer who might look at the code - there's a good chance they'd produce a deadlock by editing things carelessly. So I'm trying to simplify the use of synchronization in my code. Limiting the number of classes that use it is one aspect of this. Just bringing this up as a consideration, not a deal-breaker.
It's only the modify that's in question.
And create() and delete() if we do the sync inside Data, since Data does have these methods they should be implemented consistently with update(). And IMO read() might need some sync too, depending on how you've got things coded internally. But that's another set of discussions, nevermind...
 
Max Habibi
town drunk
( and author)
Posts: 4118
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Jim Yingst:
I though that we has always agreed that you needed to synchronized inside of lock/unlock? It's only the modify that's in question.
Right - well I wasn't sure you knew the exact requirements for NX-Contractors, so I was stating the relevant parts in case.



Ahh. I've actually done the contractors assignment


But my main point in the last point was that, given that there's synchronization inside lock() and unlock(), it may be a good idea for other synchronization (e.g. update()) to be at the same level, in the same class, rather than somewhere further removed where it's harder to see how or if it interacts with the lock/unlock. My own original design had a number of different objects I sync on for various reasons, and while I understand how they interact I have to concede that it's a bit complicated for a junior programmer who might look at the code - there's a good chance they'd produce a deadlock by editing things carelessly. So I'm trying to simplify the use of synchronization in my code. Limiting the number of classes that use it is one aspect of this. Just bringing this up as a consideration, not a deal-breaker.



That's a good point. The only things that make me suggest an alternative approach are that 1. The first assignment had synchronized methods for modify-ing methods, and 2. the explicit synchronization on the locking map that you're talking about, while very correct, effectively trivializes the lock/unlock to nothingness. It's not difficult to see an implementation that, slightly modified, removes the need for them altogether. I can't imagine that this was the goal of the assignment. However, that's very open to a different interpretation.


It's only the modify that's in question.
And create() and delete() if we do the sync inside Data, since Data does have these methods they should be implemented consistently with update(). And IMO read() might need some sync too, depending on how you've got things coded internally. But that's another set of discussions, never mind...


Don't know about read, because there seem to be build in failure mechs to protect us from dirty ones. Both approaches are reasonable, methinks.
M
 
Something about .... going for a swim. With this tiny ad ...
Smokeless wood heat with a rocket mass heater
https://woodheat.net
reply
    Bookmark Topic Watch Topic
  • New Topic