Is this a fair analogy to make? I think I can think of some scenarios where you have to use locks, but I'm not sure if they're necessary.
For instance, here is a loop I recentl开发者_运维问答y wrote to perform waiting on a thread to update a list. I apologize if this is terrible java, as I am a native linux kernel-ite:
ReentrantLock lock;
...
while(true) {
lock.lock();
int size = queue.size();
if (size == 0) {
try {
lock.unlock();
Thread.sleep(3600000);
continue;
} catch (InterruptedException e) {
lock.lock();
size = queue.size();
if (size == 0) {
lock.unlock();
continue;
}
}
}
VPacket p = queue.getFirst();
lock.unlock();
return p;
}
Where VPacket
is a packet for the particular protocol I'm writing.
This analogy came to mind as I pondered the Collections.synchonizedList class
, thinking about how I can do this without such C-like tricks.
Well, synchronized blocks predate classes like ReentrantLock by many years; the classes in that package were introduced to provide some more sophisticated and higher-level capabilities than what Java the language had previously offered -- although many of those capabilities are needed only in very specific circumstances.
But in this specific case, I would say that using a synchronized block (and wait(N) instead of sleep(N)!) would be more elegant. I understand your analogy, and I'd say that sometimes it holds; certainly for this sort of run-of-the-mill-case, using a synchronized block is like using the RAII pattern in C++ -- it's the clearest way to make sure things are cleaned up when needed.
In general, designing concurrent data structures that behave (and perform) well is a very tricky task. It is considered a Good Thing(tm) if you don't implement everything yourself using low-level tools, but rather use more high-level abstractions available in the standard library.
For your task, I'd rather use a BlockingQueue or BlockingDeque which takes care of the synchronization for me.
Synchronized blocks are always properly nested by nature, while you can misuse locks and forget to unlock. However, the first thing you read about locks is to always use them in a try/finally construct, like this:
lock.lock();
try {
// Do things...
}
finally {
lock.unlock();
}
This is a strongly recommended pattern that ensures your locks behave as synchronized blocks.
Locks have several advantages over synchronized blocks:
- They are more efficient than synchronized blocks, although I read that's been fixed in the recent JVM.
- Some locks can be split into read and write locks for increased concurrency.
- They can be passed around, but then you're out of the safe pattern explained above.
So, IMHO, locks are more like a bigger gun: more powerful, but also more dangerous if you don't know how to use them.
A modified version of the same code, which more optimal IMO :) To answer you question, the analogy is wrong. gotos received a bad reputation because of the way they allowed control flows to escape, causing nightmares. Locks do not allow control to escape in the same sense. Well, you may ask what happens if I acquire a lock and refuse to release it. Not possible with sync blocks. The ideal pattern is to use a try-finally. BTW, concurrent programming is itself a nightmare which cannot be enhanced/reduced.
while(true) {
if(queue.size()==0){
Thread.sleep(36000);
}
lock.lock();
Vpacket p = null;
try {
p = null;
if(queue.size()!=0) {
p = queue.pop();
}
} finally {
lock.unlock();
if(p!=null) {
return p;
}
}
}
精彩评论