请教一个多线程的问题

连星入剑端 2017-05-07 06:40:30
网上的很普通的例子,线程交互的。
情景一,此时,使用线程对象本身作为同步对象,的object并没有实际用处,先不用理会。

public class SumThread extends Thread {
int total = 0;
private Object object;

public SumThread(Object object) {
super();
this.object = object;
}

public void run() {
synchronized (this) {
for (int i = 1; i <= 100; i++) {
total += i;
try {
System.out.println(Thread.currentThread().getName() + ":" + i);
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 通知;
//this.notify();
}
}
}




public class ThreadInteractionTest {
public static void main(String[] args) {

Object object = new Object();

SumThread st = new SumThread(object);
// 启动计算线程
st.start();
// 这里传入object和使用st似乎存在差别;
synchronized (st) {
// 注意比较有notify()和无notify()间的差别
try {
System.out.println("等待对象sum完成计算。。。");
// 等待;
st.wait();
// 超时等待;
//st.wait(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum对象计算的总和是:" + st.total);
}
}
}

此时的SumThread类中的第22行代码,写和不写是一样的,主线程都会直接结束等待状态。

作为对比,我书写了另一段代码,此时不使用this作为同步对象,而是用了一个单独的object。
情景二,此时object作为同步变量。

public class SumThread extends Thread {
int total = 0;
private Object object;

public SumThread(Object object) {
super();
this.object = object;
}

public void run() {
synchronized (object) {
for (int i = 1; i <= 100; i++) {
total += i;
try {
System.out.println(Thread.currentThread().getName() + ":" + i);
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 通知;
object.notify();
}
}
}




public class ThreadInteractionTest {
public static void main(String[] args) {

Object object = new Object();

SumThread st = new SumThread(object);
// 启动计算线程
st.start();
// 这里传入object和使用st似乎存在差别;
synchronized (object) {
// 注意比较有notify()和无notify()间的差别
try {
System.out.println("等待对象sum完成计算。。。");
// 等待;
object.wait();
// 超时等待;
//st.wait(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum对象计算的总和是:" + st.total);
}
}
}

此时的SumThread类中的第22行代码,写和不写是有区别的,如果不写,将无法从等待中恢复。

现在的问题是,情景一中,主线程wait()后,并未接受到notify(),为什么结束等待的呢?是因为线程的run()方法结束,而默认通知所有的线程?

为此,我在情景一中的SumThread类的代码的22行后添加了这样一段。

System.out.println("线程沉睡");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

看起来确实看起来像是如此,但并未在任何资料上找到此类说明。
具体情况是如何的呢?请各位指教。
...全文
618 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
连星入剑端 2018-05-10
  • 打赏
  • 举报
回复
一年前的帖子,做了结帖,最终给个结论(至少是我认为正确的结论)。 之所以使用Thread本身作为同步变量时,notify()不写也会使得主线程恢复运行,是因为在线程终结时,会自动调用notifyAll()方法。 参见Thread的源码,

    /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }
以上的代码,说明在线程终止时,系统会调用exit()方法。 而在exit()方法中,也就是代码的第7行,会调用group.threadTerminated(this); 这个方法的源代码如下:

    /**
     * Notifies the group that the thread {@code t} has terminated.
     *
     * <p> Destroy the group if all of the following conditions are
     * true: this is a daemon thread group; there are no more alive
     * or unstarted threads in the group; there are no subgroups in
     * this thread group.
     *
     * @param  t
     *         the Thread that has terminated
     */
    void threadTerminated(Thread t) {
        synchronized (this) {
            remove(t);

            if (nthreads == 0) {
                notifyAll();
            }
            if (daemon && (nthreads == 0) &&
                (nUnstartedThreads == 0) && (ngroups == 0))
            {
                destroy();
            }
        }
    }
在上面这段代码中的第17行,会看notifyAll();的调用。 同时,可以参看join()方法的源代码中,你也会看到wait()方法的执行,那么什么时候当前线程会被唤醒呢,应该也是在线程终结时。

    /**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
Hansy Chen 2017-05-10
  • 打赏
  • 举报
回复
public final native void wait(long timeout) throws InterruptedException; /** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object, or * some other thread interrupts the current thread, or a certain * amount of real time has elapsed. * <p> .... 这个是源码注释:其中一个意思是说当线程中断或者指定的时间到了就会释放。我想也就是你的线程执行完毕了,线程就会自动销毁,线程就中断了。所以他就释放了线程对象锁,继续执行后面的操作。 因为你情景一锁的是线程,当线程结束后自然会释放锁,而你情景二锁的是对象,因为对象一直存在你的主线中,所以只能等了。 你可以加入下面代码试试看看不一样的结果。 st.start(); Thread.sleep(1000); // 这里传入object和使用st似乎存在差别; synchronized (st) {
zhujinqiang 2017-05-10
  • 打赏
  • 举报
回复
参考: http://blog.csdn.net/u012179540/article/details/40685207
连星入剑端 2017-05-10
  • 打赏
  • 举报
回复
引用 8 楼 zhujinqiang 的回复:
参考: http://blog.csdn.net/u012179540/article/details/40685207
没有在给出的链接里,看到解决当前问题的信息呢?是哪一段?
连星入剑端 2017-05-10
  • 打赏
  • 举报
回复
引用 4 楼 shayboke 的回复:
public final native void wait(long timeout) throws InterruptedException; /** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object, or * some other thread interrupts the current thread, or a certain * amount of real time has elapsed. * <p> .... 这个是源码注释:其中一个意思是说当线程中断或者指定的时间到了就会释放。我想也就是你的线程执行完毕了,线程就会自动销毁,线程就中断了。所以他就释放了线程对象锁,继续执行后面的操作。 因为你情景一锁的是线程,当线程结束后自然会释放锁,而你情景二锁的是对象,因为对象一直存在你的主线中,所以只能等了。 你可以加入下面代码试试看看不一样的结果。 st.start(); Thread.sleep(1000); // 这里传入object和使用st似乎存在差别; synchronized (st) {
对了,还有,你提示的在start()后添加sleep,在情景一中没有观察出什么影响; 反而是在情景二中,由于当前代码并没有按标准的while循环来写,会因为sleep()的这1秒,使得st执行完成后,主线程才执行。导致notify()执行在前,而wait()执行在后,主线程因无法被唤醒而持续等待的情况,也就是信号错失。 这种情景在《Java编程思想(第4版)》的706页有做分析。
连星入剑端 2017-05-10
  • 打赏
  • 举报
回复
引用 4 楼 shayboke 的回复:
public final native void wait(long timeout) throws InterruptedException; /** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object, or * some other thread interrupts the current thread, or a certain * amount of real time has elapsed. * <p> .... 这个是源码注释:其中一个意思是说当线程中断或者指定的时间到了就会释放。我想也就是你的线程执行完毕了,线程就会自动销毁,线程就中断了。所以他就释放了线程对象锁,继续执行后面的操作。 因为你情景一锁的是线程,当线程结束后自然会释放锁,而你情景二锁的是对象,因为对象一直存在你的主线中,所以只能等了。 你可以加入下面代码试试看看不一样的结果。 st.start(); Thread.sleep(1000); // 这里传入object和使用st似乎存在差别; synchronized (st) {
感谢提醒,光顾着在网上找答案了,没有注意java自己的文档注释。

    public final void wait() throws InterruptedException {
        wait(0);
    }
这是Object类中源码,调用的是:

public final native void wait(long timeout) throws InterruptedException;
这段代码注释中,确实有如你所提示的这一段: This method causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. Thread T becomes disabled for thread scheduling purposes and lies dormant until one of four things happens: •Some other thread invokes the notify method for this object and thread T happens to be arbitrarily chosen as the thread to be awakened. •Some other thread invokes the notifyAll method for this object. •Some other thread interrupts thread T. •The specified amount of real time has elapsed, more or less. If timeout is zero, however, then real time is not taken into consideration and the thread simply waits until notified. 重要的是第3条,也就是粗体标识的这一条。问题是这里的字面含义翻译过来是: 其它线程中断了线程T(当前线程,也就是上面的主线程)。 这里,主线程似乎没有被中断啊? 而最后一句也在说,如果超时时间为0,线程只做等待,直到被通知。
连星入剑端 2017-05-10
  • 打赏
  • 举报
回复
引用 3 楼 blackwq 的回复:
[quote=引用 2 楼 zcpvn 的回复:] 我试了试情景二,并输出了锁对象的toString方法: 可见,两个是一样的,所以情景二是同步的,没问题。 然后我在情景一中做了一样的方法后发现输出是这样的: 可以看见,虽然线程名字是相同的,但是,线程组不同,所以我猜:情景一的代码不是同步的,那么notify()方法执不执行就不重要了。
1.情景1的代码是同步的 ,不然那个:st.wait()是无法起作用的; 2.情景1的主线程是在支线程的同步锁释放后就开始执行了 这点 LZ可以把 System.out.println("线程沉睡"); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } 这段代码放到run方法里面同步锁后面执行一下就知道了[/quote] 情景一,之所以最后加上上面这段,就是为了比较在没有notify()和有notify()的区别,这点在前面的提问中可能没说清楚,补充下。 在有notify()的情况下,在notify()后直接就输出了结果,没有等待这5秒; 在没有notify()的情况下,在等待5秒后才做的输出,也即线程结束后才有结果;
zcp_c 2017-05-09
  • 打赏
  • 举报
回复
我试了试情景二,并输出了锁对象的toString方法:

可见,两个是一样的,所以情景二是同步的,没问题。

然后我在情景一中做了一样的方法后发现输出是这样的:

可以看见,虽然线程名字是相同的,但是,线程组不同,所以我猜:情景一的代码不是同步的,那么notify()方法执不执行就不重要了。
chenchangfeng1 2017-05-09
  • 打赏
  • 举报
回复
尼玛 这么好问题没人回答啊
QTQ_WQ 2017-05-09
  • 打赏
  • 举报
回复
引用 2 楼 zcpvn 的回复:
我试了试情景二,并输出了锁对象的toString方法: 可见,两个是一样的,所以情景二是同步的,没问题。 然后我在情景一中做了一样的方法后发现输出是这样的: 可以看见,虽然线程名字是相同的,但是,线程组不同,所以我猜:情景一的代码不是同步的,那么notify()方法执不执行就不重要了。
1.情景1的代码是同步的 ,不然那个:st.wait()是无法起作用的; 2.情景1的主线程是在支线程的同步锁释放后就开始执行了 这点 LZ可以把 System.out.println("线程沉睡"); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } 这段代码放到run方法里面同步锁后面执行一下就知道了

62,623

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧