java多线程中volatile的理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010285684/article/details/80090001

一、使用Runnable来实现多线程

package com.xiancheng.old;
/**
 * 使用Runnable的方式来实现多线程<BR>
 * 开发者 : SGX <BR>
 * 时间:2018年4月26日 上午9:51:31 <BR>
 * 变更原因: <BR> 
 * 首次开发时间:2018年4月26日 上午9:51:31 <BR>
 * 版本:V1.0
 */
public class ThreadOld implements Runnable {
    
    private boolean falg = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        falg = true;
    }

    public boolean isFalg() {
        return falg;
    }

    public void setFalg(boolean falg) {
        this.falg = falg;
    }
    
}

二、测试

  1. 在没有使用synchronized或轻量组锁volatile的代码如下
    /**
     * 需求:当flag为真的时候结束程序。
     * 
     * @Author: SGX <BR>
     * @Datetime:2018年4月26日 上午9:53:21 <BR>
     */
    @Test
    public void test() {
        ThreadOld old = new ThreadOld();
        new Thread(old).start();
        // 无限循环
        for (;;) {
            // 当flag为true的时候就结束程序
            // 这个时候出了问题,就是flag在子线程更改后,写入主线程后,主线读取不到,这就是内存可见性问题
            // 针对这个问题,1.可以用同步代码块,2.可以用轻量级锁volitle来解决
            if (old.isFalg()) {
                assertTrue("当为真的时候应该结束程序", old.isFalg());
                break;
            }
        }
    }

运行这个代码的时候就出现了问题,程序迟迟不会结束。


造成这个问题的原因是什么呢。先看主线程和子线程内存模型


子线程已经把falg改为true了,为什么主线程读取不到呢,因为我们的子线程在改动falg的值后没有告诉主线程,主线程拿不到已经已经修改后的falg的值。所以解决方法如下。

1. 用同步代码块

    public class ThreadOld implements Runnable {
    private boolean falg = false;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        falg = true;
    }

    public boolean isFalg() {
        return falg;
    }

    public void setFalg(boolean falg) {
        this.falg = falg;
    }
    
}
    @Test
    public void test() {
        ThreadOld old = new ThreadOld();
        new Thread(old).start();
        // 无限循环
        for (;;) {
            synchronized(old) {
            // 当flag为true的时候就结束程序
            // 这个时候出了问题,就是flag在子线程更改后,写入主线程后,主线读取不到,这就是内存可见性问题
            // 针对这个问题,1.可以用同步代码块,2.可以用轻量级锁volitle来解决
            if (old.isFalg()) {
                assertTrue("当为真的时候应该结束程序", old.isFalg());
                break;
            }
            }
        }
    }

2. 用volatile关键字

public class ThreadOld implements Runnable {
    
    private volatile boolean falg = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        falg = true;
    }

    public boolean isFalg() {
        return falg;
    }

    public void setFalg(boolean falg) {
        this.falg = falg;
    }
    
}
    @Test
    public void test() {
        ThreadOld old = new ThreadOld();
        new Thread(old).start();
        // 无限循环
        for (;;) {
            // 当flag为true的时候就结束程序
            // 这个时候出了问题,就是flag在子线程更改后,写入主线程后,主线读取不到,这就是内存可见性问题
            // 针对这个问题,1.可以用同步代码块,2.可以用轻量级锁volitle来解决
            if (old.isFalg()) {
                assertTrue("当为真的时候应该结束程序", old.isFalg());
                break;
            }
        }
    }

三、内存可见性问题的解决

1. 内存可见性(Memory Visibility)当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。

2. 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。

3. 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile 变量。

4.为什么volatile关键字可以有这样的特性?
这是因为java语言的先行发生原则(happens-before)。先行发生原则是两个事件的结果之间的关系,如果一个事件发生在另一个事件之前,结果必须反映给另一个事件。这里所谓的事件,实际上就是各种指令操作,比如读操作、写操作、初始化操作、锁操作等等。先行发生原则作用于很多场景下,包括同步锁、线程启动、线程终止、volatile。(这段话摘自程序员小灰微信公众号)

四、为什么要使用volatile

比原来的synchroized轻量,效率高,对于多线程不是一各互斥关系,保证程序指令不重排序

五、volatile的业务使用

1.运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

2.但是不能变量的原子性操作。变量不需要与其他的状态变量共同参与不变约束(状态)。如下代码

public class VolatileAtomicity {

    private volatile static int start = 2;
    private volatile static int end = 4;

    public static void main(String[] args) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (start < end) {
                    System.out.println(start + " : " + end);
                } 
                System.exit(0);
            }
        }, "线程一").start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                for(;;) {
                start += 2;
                end += 2;
                }
            }
        }, "线程二").start();
    }
}
while(start < end)会在某个瞬间造成start==end,从而结束程序。




猜你喜欢

转载自blog.csdn.net/u010285684/article/details/80090001