狐言不胡言

导航

线程安全之可见性(三)

一:final的处理

     1.1 经final修饰的变量或者对象,在其构造函数中初始化之后,其他线程一定可以获得正确的构造版本,即可以获得变量或者对象字段的最新值。

看下面的代码:

public class ThreadFinal1 {

    public final int i;
    public int j;

    public ThreadFinal1 final1;

    public ThreadFinal1() {
        this.i = 2; //一定可以得到正确的构造版本
        this.j = 3; //不一定得到正确的构造版本,可能得到默认值0
    }

    public void write() {
        final1 = new ThreadFinal1();
    }

    public void reader() {
        if (final1 != null) {
            System.out.println(final1.i);
            System.out.println(final1.j);
        }
    }
}

       上述代码,定义了一个final修饰的i,和一个没用final修饰的j,在构造函数中赋值后,i在其他线程中一定可以获得正确的构造版本,即i=2,而j不一定可以获得正确的构造版本,可能获得j的默认值0。

       1.2 经final修饰的变量或者对象,在其构造函数中初始化之后,其他线程一定可以获得正确的构造版本,而且字段若重新赋值与final修饰的字段,那么该字段也一定可以获得正确的构造版本。

public class ThreadFinal2 {

    public final int i;
    public int j;

    public ThreadFinal1 final1;

    public ThreadFinal2() {
        this.i = 2; //一定可以得到正确的构造版本
        this.j = this.i; //也一定可以得到正确的构造版本
    }

    public void write() {
        final1 = new ThreadFinal1();
    }

    public void reader() {
        if (final1 != null) {
            System.out.println(final1.i);
            System.out.println(final1.j);
        }
    }
}

上述代码中,j赋值为i,i是final修饰的字段,那么j在其他线程中也一定可以获得正确的构造版本,即j=2。

二:字节分裂(word tearing)

       在有些处理器中,不能对单个的字节进行修改,这样就会造成一些问题;如字节数组,多个线程想要去修改它,只能通过把该字节数组copy过来修改,然后再把主内存中的字节数组覆盖掉,这样会造成只能有一个线程是修改成功的。

       在上图中,线程1和线程2去修改字节数组,分别修改不同位置的值,这样就会造成最后只有一个线程修改成功。所以,在多线程编程中,尽量不要去对byte[]数组修改。

三:double和long的特殊处理

       double和long都是64位字节,他们的特殊在于,要去修改值,是分成两部分进行的,即分成两个32位字节的,这样就会导致,出现脏数据,如一个线程修改了值,另一个线程读取,可能读到的是只修改一部分的值。

        所以在多线程编程中,可以用volidate字段去修饰double和long,并不是说volidate修饰的字段是原子性的,只是在double和long中,用volidate修改,可以保证其原子性。

posted on 2021-04-16 15:01  狐言不胡言  阅读(66)  评论(0编辑  收藏  举报