Java线程join()与睡眠间隔导致问题

时间:2018-10-10 09:41:16

标签: java multithreading concurrency

我正在做有关并发编程的教程,看到.join()方法用于确保线程将运行直到完成,然后再继续其余代码。

但是,当我尝试使用2个线程来递增和递减一个变量,例如100次(初始值:2000)时,最终结果应该显示为2000,但它显示大于2000或小于2000。 当我在 main 方法中取消对Thread.sleep的注释时,它只能正常工作并始终显示2000。

public class Main {
    public static void main(String[] args) {
        SharedObj sharedObj = new SharedObj();
        Thread thread1 = new Thread(new ThreadMinus(sharedObj));
        Thread thread2 = new Thread(new ThreadPlus(sharedObj));
        thread1.start();
//        try {
//            Thread.sleep(100);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("number is: " + sharedObj.num);
        System.out.println("increment count: " + sharedObj.countPos);
        System.out.println("decrement count: " + sharedObj.countNeg);

    }
}

class SharedObj {
    public int num;
    public int countPos = 0;
    public int countNeg = 0;

    public SharedObj() {
        num = 2000;
    }

    public void change(int x) {
        num += x;
        if (x < 0) {
            countNeg++;
        } else {
            countPos++;
        }
        System.out.println("number is: " + num + " with operation: " + x);
    }
}

class ThreadMinus implements Runnable {
    SharedObj sharedObj;

    public ThreadMinus(SharedObj sharedObj) {
        this.sharedObj = sharedObj;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            sharedObj.change(-1);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class ThreadPlus implements Runnable {
    SharedObj sharedObj;

    public ThreadPlus(SharedObj sharedObj) {
        this.sharedObj = sharedObj;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            sharedObj.change(+1);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4 个答案:

答案 0 :(得分:2)

这不是睡眠/联接引起的问题。在竞争条件下,您要递增相同的变量而不保护它。将synchronized添加到SharedObj#change,就可以了:

public synchronized void change(int x) { //...

答案 1 :(得分:2)

您看到的问题是竞态条件的典型案例。 考虑num += x;对您来说似乎是一项操作,但实际上它是CPU中的多项操作。

简单来说  1. temp1 = num;  2. temp2 = x;  3. temp3 = temp1 + temp2  4. num = temp3

它实际上发生在CPU的寄存器中,因此在编写Java时显然看不到它。

当多个线程同时运行并执行这些指令时,可能会导致意外结果。

例如,让我们以x = 1和num = 2开头。

线程A正在执行:

 1. temp1a = num; 
 2. temp2a = x; 
 3. temp3a = temp1a + temp2a; 
 4. num = temp3a; 

线程B正在执行

 1. temp1b = num;
 2. temp2b = x;
 3. temp3b = temp1b + temp2b;
 4. num = temp3b;

现在,如果他们同时执行这些操作,您可能会按照以下顺序结束:

 1. temp1a = num; //2
 2. temp2a = x; //1
 3. temp3a = temp1a + temp2a; //3

 4. temp1b = num; //2
 5. temp2b = x; //1
 6. temp3b = temp1b + temp2b; //3
 7. num = temp3b; //3

 8. num = temp3a; //3

因此,尽管您增加了num两次,但结果为3,而不是4。 那是比赛条件。避免它的方法有很多,同步是其中一种,但是还有其他技术。

如果您想更好地理解它,我真的推荐这门在线课程。 https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

这真的很便宜,您可以在几个小时内完成它,但是它完全从零开始用示例很好地解释了它,并且它涉及到并发性非常高级的主题。这是一笔不错的时间投资。

在您的情况下,只需像这样添加同步:

public synchronized void change(int x) {
    num += x;
    if (x < 0) {
        countNeg++;
    } else {
        countPos++;
    }
    System.out.println("number is: " + num + " with operation: " + x);
}

将解决问题,但这不是灵丹妙药。您应该花点时间了解为什么在这种情况下可以解决问题,而不能在另一情况下解决。

希望对您有帮助。

答案 2 :(得分:1)

之所以发生这种情况,是因为您尝试同时更新同一变量的值。通过观察输出,您可以发现由于这种并发性,程序有时无法更新值。您可以通过将方法“更改”设置为“同步”或增加每个线程的睡眠时间(将睡眠保持在Main方法中)来解决此问题。

答案 3 :(得分:0)

join()位置错误。

 public class ThreadJoinTest {
    public static void main(String[] args) throws InterruptedException {
        SharedObj sharedObj = new SharedObj();
        Thread thread1 = new Thread(new ThreadMinus(sharedObj));
        Thread thread2 = new Thread(new ThreadPlus(sharedObj));
        thread1.start();
        thread1.join();
//        try {
//            Thread.sleep(100);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        thread2.start();
        thread2.join();
//        try {
//            thread1.join();
//            thread2.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println("number is: " + sharedObj.num);
        System.out.println("increment count: " + sharedObj.countPos);
        System.out.println("decrement count: " + sharedObj.countNeg);

    }
}

class SharedObj {
    public int num;
    public int countPos = 0;
    public int countNeg = 0;

    public SharedObj() {
        num = 2000;
    }

    public void change(int x) {
        num += x;
        if (x < 0) {
            countNeg++;
        } else {
            countPos++;
        }
        System.out.println("number is: " + num + " with operation: " + x);
    }
}

class ThreadMinus implements Runnable {
    SharedObj sharedObj;

    public ThreadMinus(SharedObj sharedObj) {
        this.sharedObj = sharedObj;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            sharedObj.change(-1);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class ThreadPlus implements Runnable {
    SharedObj sharedObj;

    public ThreadPlus(SharedObj sharedObj) {
        this.sharedObj = sharedObj;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            sharedObj.change(+1);
            try {
                Thread.sleep(50);`enter code here`
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}