2014-10-07 56 views
5
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线程同步 - 什么时候一个线程释放物体

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

+0

看看这个http://stackoverflow.com/questions/18635616/yield-inside-synchronized-block-lock-release-after-calling-yield – SimY4 2014-10-07 13:57:45

回答

5

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

的情况下让你的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 - 您可以清楚地看到它如何继续。

+0

是的,但如果是这样的话,程序的输出不应该是这样的:1 1 2 2 3 3 4 4 5 5.但通常这是输出。 – sam 2014-10-07 14:13:47

+7

@javaTech - 没有合同说Java线程应该公平。几乎任何顺序都可能是你的代码的结果。 1 1 2 2 ...非常有效。 “ – OldCurmudgeon 2014-10-07 14:20:22

1

看起来你可能对于synchronized和yield关键词的含义有些混淆。

同步意味着一次只有一个线程可以输入该代码块。把它想象成一个大门,你需要一个钥匙才能通过。它进入的每个线程都只有一个密钥,并在完成后返回。这允许下一个线程获取密钥并执行其中的代码。无论它们在同步方法中多久都没关系,一次只能有一个线程进入。

产量向编译器提出建议(并且是唯一的建议),即当前线程可以放弃其分配的时间,并且另一个线程可以开始执行。然而,这并不总是以这种方式发生。

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

不可预知的行为来自收益率不会像你预测的那样放弃执行时间。

希望有帮助!

+0

”同步意味着一次只有一个线程可以进入该代码块。“只有当代码每次进入块时都总是试图锁定_same object_对象。如果foo不是常量,多个线程可以输入相同的'synchronized(foo){...}'块。 – 2014-10-07 18:48:10

+0

“收益表明......编译器......”不是编译器。编译器不知道Thread.yield()和其他方法调用之间的区别。神奇(如果有的话)在运行时发生,很可能是JVM实现中的本地线程进行某种“yield”系统调用时。 – 2014-10-07 21:44:56

+2

“不可预知的行为来自收益不放弃......”我不这么认为,正如你所指出的那样,yield()调用发生在同步块内部。无论它是否执行任何操作,其他线程都不会在yield()返回之前运行。不可预测性来自于有两个线程反复争夺相同的锁。假设线程A有锁,线程B正在等待锁。当线程A释放锁,然后_immediately_试图再次锁定它时,哪个线程获取锁?这是什么是不可预测的。 “ – 2014-10-07 21:48:46

2

当你说:

按我的理解,当一个线程启动它获取的push方法的锁。

这是不完全正确的,因为锁不仅仅是推的方法。推送方法使用的锁定位于MyStack2的实例上,该实例被调用。方法pop和toString使用与push相同的锁。当一个线程在一个对象上调用这些方法时,它必须等到它可以获得该锁。调用推送过程中的线程将阻止另一个线程调用pop。线程正在调用不同的方法来访问相同的数据结构,对访问该结构的所有方法使用相同的锁来防止线程同时访问数据结构。

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

+1

”锁定可以防止数据结构被同时访问。“小心!新手可能不明白,它不是保护数据的锁定。他们需要被告知(有时不止一次),保护数据的原因是每一个更新或使用数据的代码块都会锁定同一个锁。 – 2014-10-07 18:44:51

+0

@詹姆斯:同意了,重写了。 – 2014-10-07 18:47:10