没有 static 的 volatile 关键字不能按预期工作

Volatile keyword without static not working as expected

我了解变量的 volatile 和 static 关键字之间的区别。

static 变量可以被不同的实例改变,而 Volatile 变量可以被不同的线程改变。

但是如果我删除 MY_INT 变量的 static 关键字,下面的程序(从互联网复制并稍作修改)就会挂起。

变量 MY_INT 的更新应该被其他线程看到,即使没有静态关键字。但是如果我删除静态它会挂起。

请帮助我理解这个问题。

public class PrintOddAndEven extends Thread {
    static volatile  int i = 1;

    Object lock;

    PrintOddAndEven(Object lock) {
        this.lock = lock;
    }

    public static void main(String ar[]) {
        Object obj = new Object();
        PrintOddAndEven odd = new PrintOddAndEven(obj);
        PrintOddAndEven even = new PrintOddAndEven(obj);
        odd.setName("Odd");
        even.setName("Even");
        odd.start();
        even.start();
    }

    @Override
    public void run() {
        while (i <= 10) {
            if (i % 2 == 0 && Thread.currentThread().getName().equals("Even")) {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " - " + i);
                    i++;
                    lock.notify();
                }
            }
            if (i % 2 == 1 && Thread.currentThread().getName().equals("Odd")) {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " - " + i);
                    i++;

                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

您的错误是因为如果您从字段 i 中删除 static 关键字,您将有一个 不同的 字段 i 每个 PrintOddAndEven 的实例 所以这里因为你有 2 个实例,所以你有 2 个不同的字段 i 这样 Even 线程将永远循环,因为它的 i 永远不会被修改,并且 Odd 线程出于同样的原因永远等待。当您将字段声明为 static 时,线程共享 相同的字段 i 这样您就不会遇到错误。

您应该创建一个专用的 class 来保存您的计数器,并将它的一个实例用作您将在 PrintOddAndEven 个实例之间共享的对象监视器,如下所示:

public class MyClass {
    volatile int i = 1;
}

public class PrintOddAndEven extends Thread {

    MyClass lock;

    PrintOddAndEven(MyClass lock) {
        this.lock = lock;
    }

    public static void main(String[] args) throws Exception {
        MyClass obj = new MyClass();
        PrintOddAndEven odd = new PrintOddAndEven(obj);
        PrintOddAndEven even = new PrintOddAndEven(obj);
        odd.setName("Odd");
        even.setName("Even");
        odd.start();
        even.start();
    }

    @Override
    public void run() {
        while (lock.i <= 10) {
            if (lock.i % 2 == 0 && Thread.currentThread().getName().equals("Even")) {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " - " + lock.i);
                    lock.i++;
                    lock.notify();
                }
            }
            if (lock.i % 2 == 1 && Thread.currentThread().getName().equals("Odd")) {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " - " + lock.i);
                    lock.i++;

                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

如果您只有一个计数器,您还可以考虑使用class AtomicInteger 的实例作为计数器和对象监视器。除了您将使用 new AtomicInteger(1) 创建一个实例以将计数器初始化为 1 然后使用 get() 获取当前值和 incrementAndGet() 之外,代码将与上面相同增加计数器。

你已经创建了 2 个 PrintOddAndEven 奇数和偶数线程对象,如果你从此语句中删除 Static 关键字 volatile int i = 1; i 没有保持 class 级别,因此每个线程对象都有自己的 i 副本,当奇数线程执行它时,它会更新 odd.i++。但是 even.i 保持为 1,并且条件未通过并且它接缝表明您的线程在没有静态的情况下挂起。

public class PrintOddAndEven extends Thread {
//static volatile  int i = 1;

Lock lock;

PrintOddAndEven(Lock lock) {
    this.lock = lock;
}


static class Lock {
     volatile  int i = 1;
}
public static void main(String ar[]) {
    Lock obj = new lock();
    PrintOddAndEven odd = new PrintOddAndEven(obj);
    PrintOddAndEven even = new PrintOddAndEven(obj);
    odd.setName("Odd");
    even.setName("Even");
    odd.start();
    even.start();
}

@Override
public void run() {
    while (lock.i <= 10) {
        if (lock.i % 2 == 0 && Thread.currentThread().getName().equals("Even")) {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " - " + lock.i);
                lock.i++;
                lock.notify();
            }
        }
        if (lock.i % 2 == 1 && Thread.currentThread().getName().equals("Odd")) {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " - " + lock.i);
                lock.i++;
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}}

这样你就可以在两个线程之间共享 locki