如果Java线程在这种情况下不应该表现得如此不同,为什么会如此不同?

时间:2019-01-13 09:17:45

标签: java multithreading thread-synchronization

我有一个线程睡眠问题。在线程运行方法内部,我有一个同步块和一个睡眠时间。 每个线程以5个单位递增或递减共享类“值”,然后进入休眠状态。

public class borr {

    public static void main(String[] args) {

        int times=5;
        int sleeptime=1000;
        int initial=50;
        Shared shared = new Shared(initial);

        ThreadClass tIncrement = new ThreadClass(shared,times,sleeptime,true);
        ThreadClass tDecrement = new ThreadClass(shared,times,sleeptime,false);
        tIncrement.start();
        tDecrement.start();
    }
}

class Shared{

    int  value=0;

    public Shared(int value) {
        super();
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

class ThreadClass extends Thread{

    Shared shared;
    int times=0;
    int sleeptime=0;
    boolean inc;

    public ThreadClass(Shared shared, int times, int sleeptime, boolean inc) {
        super();
        this.shared = shared;
        this.times = times;
        this.sleeptime = sleeptime;
        this.inc = inc;
    }

    public void run() {

        int aux;

        if(inc) {
            for(int i=0;i<times;i++) {
                synchronized(shared) {
                    aux=shared.getValue()+1;
                    shared.setValue(aux);
                    System.out.println("Increment, new value"+shared.getValue());

                    try {
                        Thread.sleep(sleeptime);
                    }catch(Exception e) {
                        e.printStackTrace();
                    }
                }
            }   
        }
        else {
            for(int i=0;i<times;i++) {
                synchronized(shared) {
                    aux=shared.getValue()-1;
                    shared.setValue(aux);
                    System.out.println("Decrement, new value"+shared.getValue());

                    try {
                        Thread.sleep(sleeptime);
                    }catch(Exception e) {
                        e.printStackTrace();
                    }
                }
            }   
        }
    }
}

但是,如果像这样将Thread.sleepsynchronized块中移出,则输出为增量,减量,增量,减量。当它停止睡眠并开始循环的新迭代时,另一个线程是否不应该尝试进入?相反,它将继续循环直到该线程完成:

for(int i=0;i<times;i++) {
    synchronized(shared) {
        aux=shared.getValue()-1;
        shared.setValue(aux);
        System.out.println("Decrement, new value"+shared.getValue());
    }

    try {
        Thread.sleep(sleeptime);
    }catch(Exception e) {
        e.printStackTrace();
    }
}   

3 个答案:

答案 0 :(得分:2)

这很糟糕:

for(...) {
    synchronized(some_lock_object) {
        ...
    }
}

不好的原因是,一旦某个线程A进入该循环,然后每次解锁该锁,它要做的第二件事就是再次锁定该锁。

>

如果循环体花费大量时间执行,那么等待锁定的任何其他线程B将被操作系统置于等待状态。每次线程A释放锁时,线程B将开始唤醒,但是线程A将能够在线程B得到机会之前重新获取它。

这是 starvation 的经典示例。

解决该问题的一种方法是使用带有{em> ReentrantLockfair ordering policy而不是使用synchronized块。当线程争夺公平锁时,赢家始终是等待时间最长的赢家。

但是,公平锁的实现成本很高。更好的解决方案是始终将任何synchronized块的主体保持得尽可能短且尽可能甜。通常,线程应保持锁定状态的锁定时间不长于在某个对象中分配少量字段所需的时间。

答案 1 :(得分:1)

  

但是,如果像这样将Thread.sleep从同步块中移出,则输出为增量,减量,增量,减量。睡眠仍在循环的每个迭代内,所以,在两种情况下结果是否应该相同?:

     

当它停止睡眠并开始循环的新迭代时,另一个线程不应尝试进入。

他们都是尝试输入。

另一个已经处于等待状态(即未处于主动运行状态),因为它试图先进入。而刚刚释放锁的线程可以继续运行,并立即获得现在无可争议的锁。

这是比赛条件。当两个线程同时需要锁定时,系统可以自由选择一个。似乎选择了一些之前发布的说明。也许您可以通过yield() ing来更改此设置。也许不吧。但是,无论哪种方式,都没有指定/确定/公平。如果您关心执行顺序,则需要自己明确安排时间。

答案 2 :(得分:1)

在变体A中,您使用了两个线程...

  • 重复5次
    • 输入同步块
      • 增加
      • 等待1秒
  • 重复5次
    • 输入同步块
      • 减量
      • 等待1秒

在变体B中,您使用了两个线程...

  • 重复5次
    • 输入同步块
      • 增加
    • 等待1秒
  • 重复5次
    • 输入同步块
      • 减量
    • 等待1秒

在变体A中,两个线程始终处于活动状态(=保持在同步块中)。

在变体B中,两个线程大部分时间都在休眠。

由于绝对不能保证接下来执行哪个线程,因此变体A和B的行为如此不同也就不足为奇了。虽然在A中两个线程在理论上都可以并行活动,但第二个线程却没有太多活动机会,因为不在同步上下文中不能保证在那一刻执行上下文切换(并且运行另一个线程) )。在完全不同的变体B中:由于两个线程大多数时候都处于睡眠状态,因此运行时环境没有其他机会在睡眠时运行另一个线程。当VM尝试充分利用现有CPU资源时,睡眠将触发切换到另一个线程。

尽管如此:两个线程运行之后的结果将完全相同。这是您可以依靠的唯一确定性。其他一切都取决于特定的实现细节,VM将如何处理线程和同步块,甚至在不同的OS或不同的VM实施之间也可能有所不同。