英文:
How to tell if in a synchronised block in Java?
问题
这必须有一个简单的答案,但我不知道是什么... 例如,如果我在Java中执行以下操作:
class First{
public void first(){
Second second=new Second();
synchronized(this){
second.second(this);
}
second.second(this);
}
}
在 Second.second
方法中,我如何检查是否在调用该方法之前已经获得了同步锁,并在这种情况下抛出异常?例如:
class Second{
public void second(First first){
if(!/*想要测试是否已为first获取了锁,但不知道如何*/){
throw new RuntimeException("必须先获取锁!");
}
}
}
如果对上面的代码不明显,我希望第二次调用 Second.second
会抛出 RuntimeException
。
英文:
This must have a simple answer but I just don't know what it is... If I, say, do the following in Java:
class First{
public void first(){
Second second=new Second();
synchronized(this){
second.second(this);
}
second.second(this);
}
}
How do I check in Second.second
that the synchronised lock has been obtained before this method is called, and perhaps throw an exception if this is not the case? For instance:
class Second{
public void second(First first){
if(!/*want to test that lock obtained for first, but don't know how*/){
throw new RuntimeException("Must lock first!");
}
}
}
I want the second call to Second.second
to throw the RuntimeException
if this is not obvious to the code above.
答案1
得分: 1
有一个方法可以做到这一点:
public void second(First first) {
if (!Thread.holdsLock(first)) throw new IllegalStateException("需要锁");
}
**然而**,你不想要这样。
你想要的是这样的:
public void second(First first) {
synchronized (first) {
// 做一些操作
}
}
如果一个线程持有一个锁,然后再次在其上进行同步,那是没有问题的,也不会破坏任何东西:它不会花费时间,也不会使线程冻结。你可以重新获取一个你的线程已经持有的锁。Java 会维护一个计数器。(锁是可重入的)。
要求调用者获取这个锁似乎有点傻;为什么不自己获取它呢?如果调用者已经获取了它,那就没有问题。没有浪费时间,代码可以继续运行。
注:从代码风格的角度来看,抛出 RuntimeException 是不好的,而且在消息中加入感叹号是非常不好的(想想看;90% 以上的异常消息本来都会以感叹号结尾,这将会让审查日志变得非常烦人)。我认为对于像这样的早期退出,你也不需要使用大括号。所以,如果你必须使用“检查并抛出”风格,那么这段代码已经经过了一些修正,适用于你 :)
英文:
There's a method for that
public void second(First first) {
if (!Thread.holdsLock(first)) throw new IllegalStateException("Lock required");
}
HOWEVER, you don't want this.
What you want is this:
public void second(First first) {
synchronized (first) {
// do stuff
}
}
If a thread holds a lock and you then sync on that again, that's free, and doesn't break anything: It takes no time, and does not freeze up your thread. you can re-fetch a lock your thread already holds. Java maintains a counter. (locks are reentrant).
It seems silly to demand that callers acquire this lock; why not just acquire it yourself? If caller already acquired it, no problem. No time lost, code continues to function.
NB: As far as code style goes, throwing RuntimeException is bad, and putting an exclamation point in the message is very bad (think about it; 90%+ of all messages in exceptions would otherwise end in a ! and it's going to get real annoying to review logs). I'd argue that you don't need braces for early exits like this either. So if you must go with the 'check and throw' style, the snippet is written with some fixes applied for you
答案2
得分: 1
I want the second call to Second.second to throw the RuntimeException
if this is not obvious to the code above.
我希望对Second.second的第二次调用引发RuntimeException,如果对上面的代码不明显的话。
I think this is a bad idea. Either you want First to be handling the lock and second not caring, or Second to be handling the lock irrespective of whether it is First or Third.
我认为这是一个不好的想法。要么你希望First来处理锁,Second不关心,要么无论是First还是Third,都应该由Second来处理锁。
If we compare it to classes in the standard library, we might look at HashMap vs ConcurrentMap. HashMap is a non-threadsafe class - that is, it is identical to Second in your example. ConcurrentMap is a "threadsafe" class - that is, it handles its own synchronized operations.
如果我们将其与标准库中的类进行比较,我们可以看看HashMap与ConcurrentMap。HashMap是一个非线程安全的类 - 也就是说,它与你的示例中的Second类相同。ConcurrentMap是一个“线程安全”的类 - 也就是说,它处理自己的同步操作。
This really depends on what constitutes "threadsafe", so requires more knowledge of how the class is being used to know whether the ConcurrentMap method of thread-safety will actually provide thread-safety.
这实际上取决于“线程安全”的定义,因此需要更多了解类的使用方式,才能确定ConcurrentMap的线程安全方法是否真正提供了线程安全性。
Does anyone other than First have access to the same instance of Second and you are guarding against multi-threaded access from this angle? If so, the ConcurrentMap approach might be more appropriate. Do multiple operations occur on Second itself in a multi-threaded environment? If so, manually locking in First would be more appropriate.
除了First之外,是否有其他人可以访问同一个Second实例,而您正在防止多线程访问呢?如果是这样,ConcurrentMap方法可能更合适。在多线程环境中Second本身是否会发生多个操作?如果是这样,手动在First中加锁可能更合适。
Example of multiple operations on Second using map.
Map<Integer, String> map...
... // lets say map has 3 elements by this point and there are 2 threads running.
if (map.size() < 4)
{ // <--- thread may switch here, so both threads are inside the if block
map.put(map.size(), "This map is too small");
// Both threads have put in "This map is too small" to the map.
}
使用map对Second执行多个操作的示例。
Map<Integer, String> map...
... // 假设在这一点上map有3个元素,并且有2个线程在运行。
if (map.size() < 4)
{ // <--- 线程可能在此切换,因此两个线程都在if块内
map.put(map.size(), "This map is too small");
// 两个线程都向map中添加了"This map is too small"。
}
For this simple snippet, whether the map is HashMap or ConcurrentMap, we cannot prevent "This map is too small" being added twice. So despite ConcurrentMap providing "thread-safety", this code is not actually thread-safe. Hence, we need an external lock:
对于这个简单的代码片段,无论map是HashMap还是ConcurrentMap,我们都无法防止两次添加“This map is too small”。因此,尽管ConcurrentMap提供了“线程安全性”,但这段代码实际上并不是线程安全的。因此,我们需要一个外部锁:
...
synchronized (map)
{
if (map.size() < 4)
{
map.add(map.size(), "This map is too small");
}
}
因此,鉴于这种情况,ConcurrentMap将不会提供任何好处,使用更简单的HashMap将是正确的选择。
英文:
> I want the second call to Second.second to throw the RuntimeException
> if this is not obvious to the code above.
I think this is a bad idea. Either you want First to be handling the lock and second not caring, or Second to be handling the lock irrespective of whether it is First or Third.
If we compare it to classes in the standard library, we might look at HashMap vs ConcurrentMap. HashMap is a non-threadsafe class - that is, it is identical to Second in your example. ConcurrentMap is a "threadsafe" class - that is, it handles its own synchronized operations.
This really depends on what constitutes "threadsafe", so requires more knowledge of how the class is being used to know whether the ConcurrentMap method of thread-safety will actually provide thread-safety.
Does anyone other than First have access to the same instance of Second and you are guarding against multi-threaded access from this angle? If so, the ConcurrentMap approach might be more appropriate. Do multiple operations occur on Second itself in a multi-threaded environment? If so, manually locking in First would be more appropriate.
Example of multiple operations on Second using map.
Map<Integer, String> map...
... // lets say map has 3 elements by this point and there are 2 threads running.
if (map.size() < 4)
{ // <--- thread may switch here, so both threads are inside the if block
map.put(map.size(), "This map is too small");
// Both threads have put in "This map is too small" to the map.
}
For this simple snippet, whether the map is HashMap or ConcurrentMap, we cannot prevent "This map is too small" being added twice. So despite ConcurrentMap providing "thread-safety", this code is not actually thread-safe. Hence, we need an external lock:
...
synchronized (map)
{
if (map.size() < 4)
{
map.add(map.size(), "This map is too small");
}
}
So given this scenario, ConcurrentMap will provide no benefit, and using the simpler HashMap would be the correct choice.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论