Java中的线程:如何锁定对象?

33
以下功能在自己的线程中执行:
private void doSendData()
{
    try {

           //writeToFile(); // just a temporary location of a call
           InetAddress serverAddr = InetAddress.getByName(serverAddress);
           serverAddr.wait(60000);
           //Log.d("TCP", "C: Connecting...");
           Socket socket = new Socket(serverAddr, portNumber);
           socket.setSoTimeout(3000);

               try {
                //Log.d("TCP", "C: Sending: '" + message + "'");
                PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())),true);
                String message = packData();
                out.println(message);
                Log.d("TCP", "C: Sent.");
                Log.d("TCP", "C: Done.");
                connectionAvailable = true;

             } catch(Exception e) {
                 Log.e("TCP", "S: Error", e);
                 connectionAvailable = false;

               } finally {
                  socket.close();
                  announceNetworkAvailability(connectionAvailable);
                }

         } catch (Exception e) {
              Log.e("TCP", "C: Error", e);
              announceNetworkAvailability(connectionAvailable);
         }
}

当执行到serverAddr.wait(60000)这一行时,会抛出一个异常:

java.lang.IllegalMonitorStateException: object not locked by thread before wait()

有人知道如何锁定一个对象或函数以防止并发吗? 我尝试添加了一个Lock对象:

private final Lock lock = new ReentrantLock();

还有这一行

boolean locked = lock.tryLock();

我把它放在函数开头,但是它不起作用。


你想做什么?WAIT 没有任何意义... - ReneS
7个回答

65
为了在对象上调用wait(),您必须持有该对象的同步锁(尽管当线程等待时会实际释放锁):

为了在对象上调用wait()方法,必须先获取该对象的同步锁(synchronized lock)。尽管在线程等待时锁实际上被释放了,但是在调用wait()方法之前必须持有该锁。

synchronized (serverAddr) {
  serverAddr.wait();
}

我必须承认,在这种情况下,你为什么想要这样做让我感到困惑...


抱歉,只是为了明确:我理解在等待对象之前必须获取锁的原因。我不理解原帖作者实际上想做什么的逻辑...!!! - Neil Coffey
1
哦,那么我的评论被撤回了 :) - Bill K
1
+1 对于“当线程等待时,锁实际上被释放”,帮助我解决了另一个问题,而我之前并不知道(是的,我是 Java 的新手)。 - spirytus
我认为OP的目的是确保在任何给定时间只有一个连接到该地址。如果是这样,我预测它仍然不会起作用,因为每个线程看到的是不同的“serverAddr”对象,尽管我可能是错误的。 - Aquarelle

21
也许你正在寻找的方法是Thread.sleep(long)吗?这个方法会等待指定的毫秒数后再恢复执行,也就是说它会暂停线程的执行。

object.wait(long)(这是你正在使用的方法)则完全不同。它等待另一个线程中的另一个对象通知它(即:向其发送一种唤醒消息),并将最多等待指定的毫秒数。考虑到你发布的代码,我非常怀疑这是否真正是你想要的。

如果Thread.sleep() 不是你想要的,那么你应该像其他帖子中提到的那样使用同步块。


11

每次看到这种代码我都感到不舒服。给自己一个方便,看看 java.util.concurrent 包。


1
为避免该错误信息,请使用synchronized关键字:
synchronized(serverAddr){
  serverAddr.wait(60000);
}

1
以上都是正确的。你可以使用同步代码块,或者创建一个所谓的互斥锁。互斥锁实际上可以是任何对象。很多人只是使用Object本身作为互斥锁。然后你可以在互斥锁上加锁。任何想要获取访问权限的线程必须等待持有互斥锁的线程释放它。
Apocalisp也提出了一个建议。我还建议你看一下java.util.concurrent包。

0

以下代码应该可以正常工作。

     private final ReentrantLock lock = new ReentrantLock();

     lock.lock();  // block until condition holds
     try {
        serverAddr.wait(60000);
     } finally {
       lock.unlock()
     }
   }

请参考此文档页面获取更多细节。

public void lock()

获取锁。
如果另一个线程没有持有锁,则获取锁并立即返回,将锁保持计数设置为 1。
如果当前线程已经持有锁,则锁保持计数将增加 1,并立即返回。
如果锁被另一个线程持有,则当前线程会因线程调度目的而被禁用,并处于休眠状态,直到获取锁时,锁保持计数被设置为 1。
参考此 SE 问题以了解 `lock` 相对于 `synchronization` 的优点: Synchronization vs Lock

0
通常情况下,在Java中开发多线程程序时,您需要使用 synchronized(关键字)锁定共享变量,因此在任何时候只有一个线程可以访问共享内存。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接