在int变量上使用时,易失性关键字的行为不符合预期

时间:2018-07-09 09:35:07

标签: java multithreading volatile

import java.util.ArrayList;

public class ThreadTest {

    public static void main(String[] args) {

        ArrayList arr = new ArrayList();        
        for (int  i =0; i<10 ; i++)
            arr.add(new MyTh(i));   
        for (int  i =0; i<10 ; i++) {
            ((Thread ) arr.get(i)).start();
        }
    }

}

class MyTh extends Thread {

   int num;

   volatile int  age;

   MyTh (int  k)

   {this.num=k; } 
   @Override
   public void run() {
    if(this.num==4){
        setAge(40);
    }
    System.out.println(Thread.currentThread().getName() + " :"+this.num + ":" + getAge());
}
    synchronized int getAge() {
       return age;
    }
    synchronized void  setAge(int  k) {
        this.age =k;
    }
}

我在这里感到困惑,为什么程序的输出只打印一次volatile变量age = 40的值。 我试图在getter / setter中保持同步,然后也只有第4个单线程将其打印40。为什么其他后续线程也不能读取它40?

Thread-0 :0:0
Thread-1 :1:0
Thread-2 :2:0
Thread-3 :3:0
Thread-4 :4:40
Thread-5 :5:0
Thread-6 :6:0
Thread-7 :7:0
Thread-8 :8:0
Thread-9 :9:0

5 个答案:

答案 0 :(得分:1)

为什么在变量被声明为synchronized的同时,将volatile关键字用于getter和setter方法。这里不需要。您只需要可见性,而不是原子性。所以像这样更改代码,

public class MyTh extends Thread {
    int num;

    static volatile int age;

    MyTh(int k)

    {
        this.num = k;
    }

    @Override
    public void run() {
        if (this.num == 4) {
            setAge(40);
        }
        System.out.println(Thread.currentThread().getName() + " :" + this.num + ":" + getAge());
    }

    int getAge() {
        return age;
    }

    void setAge(int k) {
        age = k;
    }
}

首先将age变量标记为static,以便在类的所有实例之间共享该变量。在您以前的案例中,这是一个实例字段,因此每个实例都有自己的副本。因此,一个人所做的更改对其他人不可见。

最新输出如下。

Thread-0 :0:0
Thread-3 :3:0
Thread-2 :2:0
Thread-1 :1:0
Thread-4 :4:40
Thread-6 :6:40
Thread-8 :8:40
Thread-5 :5:40
Thread-7 :7:40
Thread-9 :9:40

答案 1 :(得分:0)

我相信您打算将年龄作为静态变量。

static volatile int age;

答案 2 :(得分:0)

您具有age的实例副本,因此它不是共享副本,请使用static关键字使age类变量

尝试下面的代码,我想知道什么

import java.util.ArrayList;

公共类MyTh扩展了Thread {     整数;

static volatile int age;

MyTh(int k)

{
    this.num = k;
}

@Override
public void run()
{
    if (this.num == 4)
    {
        setAge(40);
    }
    System.out.println(Thread.currentThread().getName() + " :" + this.num + ":" + getAge());
}

synchronized int getAge()
{
    return age;
}

synchronized void setAge(int k)
{
    this.age = k;
}

public static void main(String[] args)
{

    ArrayList arr = new ArrayList();
    for (int i = 0; i < 10; i++)
        arr.add(new MyTh(i));
    for (int i = 0; i < 10; i++)
    {
        ((Thread) arr.get(i)).start();
    }
}

}

答案 3 :(得分:0)

首先,您正在使用instance variable instead of class variable。您的意图需要后一个。

第二,您不需要在代码中使用volatile关键字,因为您已经在其getter / setter上使用了synchronizedHere is its reason.

此外,为了使您在修改arr时遇到同步问题,应该使用一种机制来克服它,例如CopyOnWriteArrayList。这是一些有问题的输出示例,

Thread-9 :9:40
Thread-6 :6:40
Thread-1 :1:0
Thread-8 :8:40
Thread-4 :4:40
Thread-2 :2:0
Thread-5 :5:40
Thread-7 :7:40
Thread-3 :3:40 // <---- ???
Thread-0 :0:0

Thread-1 :1:0
Thread-3 :3:0  // <----
Thread-8 :8:40
Thread-7 :7:40
Thread-5 :5:40
Thread-6 :6:40
Thread-2 :2:0
Thread-4 :4:40
Thread-0 :0:0
Thread-9 :9:40

Thread-1 :1:40   // <---- ??? woww
Thread-3 :3:40   // <---- ??? woww  
Thread-8 :8:40   // <----  ok
Thread-7 :7:40   // <----  ok
Thread-5 :5:40   // <----  ok
Thread-6 :6:40   // <----  ok
Thread-2 :2:40   // <---- ??? woww
Thread-4 :4:40   // <----  ok
Thread-0 :0:0
Thread-9 :9:40   // <----  ok

此处的问题是 ArrayList的操作未同步,也不是线程安全的。 CopyOnWriteArrayList可用于实现这一目标。

import java.util.concurrent.CopyOnWriteArrayList;
class ThreadTest {

    public static void main(String[] args) {

        CopyOnWriteArrayList<Thread> arr = new CopyOnWriteArrayList<>();

        for (int  i =0; i<10 ; i++)
            arr.add(new MyTh(i));

        for (int  i =0; i<10 ; i++) {
            (arr.get(i)).start();
        }
    }

}

class MyTh extends Thread {

    int num;
    static /*volatile*/ int  age;

    MyTh (int  k)

    {this.num=k; }
    @Override
    public void run() {
        if(this.num==4){
            setAge(40);
        }
        System.out.println(Thread.currentThread().getName() + " :"+this.num + ":" + getAge());
    }
    synchronized int getAge() {
        return age;
    }
    synchronized void  setAge(int  k) {
        this.age =k;
    }
}

答案 4 :(得分:0)

这里有两件事:

  1. 您正在使用实例变量 age 并创建10个MyTh类实例,因此每个实例将拥有自己的副本,并且不会互相干扰。因此,根据@tanyehzheng的建议,您必须将其设置为静态,以便可以在所有10个实例之间共享。

  2. 您关于如果我们有静态变量,volatile将产生什么变化的问题:

    • 静态仅声明该副本将在所有实例之间共享,并且在多个线程尝试更新/访问该副本时,它不保证内存可见性。
    • 另一方面,
    • volatile 将确保内存可见性,即,如果对该变量进行了任何写操作,则所有后续读取将看到最新值(“先于关系”)。如果仅使用静态,则不可能,因为CPU内核可能会缓存该值或将其存储在寄存器中。