【问题标题】:Thread synchronization- When does a thread release the lock on an object线程同步 - 线程何时释放对象上的锁
【发布时间】:2014-12-01 23:28:04
【问题描述】:
public class MyStack2 {
    private int[] values = new int[10];
    private int index = 0;

    public synchronized void push(int x) {
        if (index <= 9) {
            values[index] = x;
            Thread.yield();
            index++;
        }
    }

    public synchronized int pop() {
        if (index > 0) {
            index--;
            return values[index];
        } else {
            return -1;
        }
    }

    public synchronized String toString() {
        String reply = "";
        for (int i = 0; i < values.length; i++) {
            reply += values[i] + " ";
        }
        return reply;
    }
}

public class Pusher extends Thread {
    private MyStack2 stack;

    public Pusher(MyStack2 stack) {
        this.stack = stack;
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            stack.push(i);
        }
    }
}

public class Test {
    public static void main(String args[]) {
        MyStack2 stack = new MyStack2();
        Pusher one = new Pusher(stack);
        Pusher two = new Pusher(stack);
        one.start();
        two.start();
        try {
            one.join();
            two.join();
        } catch (InterruptedException e) {
        }
        System.out.println(stack.toString());
    }
}

由于MyStack2 类的方法是同步的,我期望输出为 1 2 3 4 5 1 2 3 4 5. 但是输出是不确定的。通常它会给出:1 1 2 2 3 3 4 4 5 5

根据我的理解,当线程一启动时,它会在push 方法上获得一个锁。在push() 内,线程一会产生一段时间。但是当yield() 被调用时它会释放锁吗?现在当线程二启动时,线程二会在线程一完成执行之前获得锁吗?谁能解释一下线程一何时释放堆栈对象上的锁?

【问题讨论】:

标签: java multithreading thread-synchronization


【解决方案1】:

synchronized 方法只会在执行时阻止其他线程执行它。一旦它返回,其他线程就可以(并且通常会立即)获得访问权限。

获得1 1 2 2 ... 的情况可能是:

  1. 线程 1 调用 push(1) 并被允许进入。
  2. 线程 2 调用 push(1) 并在线程 1 使用它时被阻塞。
  3. 线程 1 退出 push(1)
  4. 线程 2 可以访问 push 并推送 1,但同时线程 1 调用 push(2)

结果1 1 2 - 你可以清楚地看到它是如何继续的。

【讨论】:

  • 是的,但如果是这种情况,程序的输出不应该是:1 1 2 2 3 3 4 4 5 5。但通常这是输出。
  • @javaTech - 没有合同规定 Java 线程应该是公平的。几乎任何订单都可能是您的代码的结果。 1 1 2 2 ... 非常有效。
【解决方案2】:

当你说:

据我了解,当线程一启动时,它会在 push 方法上获得锁。

这不太对,因为锁不只是在 push 方法上。 push 方法使用的锁位于调用 push 的 MyStack2 实例上。 pop 和 toString 方法使用与 push 相同的锁。当一个线程在一个对象上调用这些方法中的任何一个时,它必须等待直到它可以获取锁。正在调用 push 的线程将阻止另一个线程调用 pop。线程调用不同的方法来访问相同的数据结构,对所有访问该结构的方法使用相同的锁,防止线程同时访问数据结构。

一旦一个线程在退出同步方法时放弃了锁,调度程序就会决定哪个线程下一个获得锁。您的线程正在获取锁并多次释放它们,每次释放锁时,调度程序都会做出决定。你不能对哪个会被选中做出任何假设,它可以是其中任何一个。来自多个线程的输出通常是混乱的。

【讨论】:

  • "锁定防止数据结构被同时访问。"小心!新手可能不明白,保护数据的不是锁定本身。需要告诉他们(有时不止一次),保护数据的是更新或使用数据的每个代码块都锁定同一个锁。
  • @james:同意,重新措辞。
【解决方案3】:

您似乎对 synchronized 和 yield 关键字的确切含义有些困惑。

同步意味着一次只有一个线程可以进入该代码块。把它想象成一扇门,你需要一把钥匙才能通过。每个线程在进入时获取唯一的密钥,并在完成后返回它。这允许下一个线程获取密钥并执行里面的代码。不管他们在同步方法中的时间有多长,一次只能进入一个线程。

Yield 向编译器建议(是的,它只是一个建议)当前线程可以放弃其分配的时间,而另一个线程可以开始执行。然而,情况并不总是这样。

在你的代码中,即使当前线程向编译器建议它可以放弃执行时间,但它仍然持有同步方法的关键,因此新线程无法进入。

不可预知的行为来自于 yield 没有像你预测的那样放弃执行时间。

希望有所帮助!

【讨论】:

  • “同步意味着一次只有一个线程可以进入那个代码块。”只有当代码每次进入块时总是试图锁定同一个对象时,这才是正确的。如果foo 不是常数,则可以有多个线程进入同一个synchronized(foo){...} 块。
  • "Yield 建议...给编译器..." 不是给编译器。编译器不知道 Thread.yield() 和任何其他方法调用之间的区别。魔法(如果有的话)发生在运行时,很可能是当 JVM 实现中的本机线程进行某种“yield”系统调用时。
  • “不可预知的行为来自不放弃的收益...”我不这么认为,因为正如您所指出的,yield() 调用发生在同步块内。不管它是否做任何事情,在 yield() 返回之前,另一个线程都不会运行。不可预测性来自于有两个线程反复争夺同一个锁的事实。假设线程 A 拥有锁,而线程 B 正在等待锁。当线程 A 释放锁然后立即尝试再次锁定它时,哪个线程获得了锁? 是不可预测的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-15
  • 1970-01-01
  • 2016-09-19
  • 2018-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多