没长正的技术专栏 勤动手、多思考

高并发之Happens-Before

2018-08-02

阅读:


线程的安全性问题

​ 大家都知道,线程会存在安全性问题,那接下来我们从原理层面去了解线程为什么会存在安全性问题,并且我们应该怎么去解决这类的问题。 ​ 其实线程安全问题可以总结为: 原子性可见性有序性这几个问题,我们搞懂了这几个问题并且知道怎么解决,那么多线程安全性问题也就不是问题了。

常见的解决安全性问题方案:

原子性: Synchronized、AtomicXXX、Lock (CAS)
可见性: Synchroinzed、volatile
有序性: Synchroinzed、volatile  => happens-before模型

1. Happens-Before 模型

​ 从JDK1.5 开始引入的。多线程环境下,一个操作对另一个操作可见,必然存在happens-before关系。(可见性与有序性)

1.1 程序顺序规则

as-if-serial:不管怎么重排序,单线程执行的结果都是不变的。

int a = 10; // A
int b = 18; // B
int c = a * b;  //C
//不管 A、B怎么重排序,C是不允许重排序的,结果都是不变的。
1.2 传递性原则
//如同上面的例子:
// A happens-before B
// B happens-before C

//推导出: A happens-before C   (其中A、B可以重排,不影响结果。)
1.3 volatile 变量规则

​ 对于volatile修饰的变量的写操作,一定happens-before后续对于volatile变量的读操作。底层通过内存屏障机制来防止指令重排。

public class VolatileTest{
    int a = 1;
    volatile boolean flag = false;
    public void write()
    {
        a = 2; //  1
        flag = true; // 2  后volatile写时,前面操作不能重排
    }    
    public void read(){
        if(flag)  // 3  先volatile读时,后面操作不能重排
        {
            int m = a; //4
        }
    }
    public static void main(String[] args) {
        VolatileTest vt = new VolatileTest();
        Thread t1 = new Thread(() -> {
            vt.write();
        });
        Thread t2 = new Thread(() -> {
            vt.read();
        });
//        t1.start();
//        t2.start();
        t2.start();
        t1.start();
    }
}

是否重排 第二个操作

第一个操作 普通读/写 volatile 读 volatile写
普通读/写     NO
volatile读 NO NO NO
volatile写   NO NO
赋值为写,取值为读
1.4 监视器锁规则

synchronized: 一个线程对于一个锁的释放操作,一定happen-before与后续线程对这个锁的加锁操作。

public class SynchronizedTest{
	int m =10;
    public void test(){
        synchronized(this)  //此处自动加锁
        {
            // m 是共享变量,初始值为20
            if(m < 18)
            {
                this.m = 18;
            }
        }//此处自动释放锁
    }
}
  • 假设m的初始值是10,线程A执行完代码块后,m的值会变成12,执行完成之后会释放锁。线程B进入代

    码块时,能够看到线程A对m的写操作,也就是B线程能够看到m=12。

1.5 start规则

start:如果线程A执行操作ThreadB.start(),那么线程A的ThreadB.start()之前的操作happens-before线程B中的任意操作。

public class StartTest{
  	int x=0;    
    public static void main(String[] args) {
          Thread t1=new Thread(()->{
           //主线程调用t1.start()之前
           //所有对共享变量的修改,此处皆可见   //此例中,x==20
              System.out.println(x);
          });        
        //修改共享变量的值
        x = 20;
        t1.start();
    }
}
1.6 join规则

join规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功的返回。

public class JoinTest{
    int x =0;
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
         //此处对共享变量x修改
         x=100;
        });
        //例如此处对共享变量修改,
        //则这个修改结果对线程t1可见
        //主线程启动子线程
        t1.start();
        t1.join()
        //子线程所有对共享变量的修改
        //在主线程调用t1.join()之后皆可见//此例中,x==100
    }
}

参考:

GP-Mic

ConcurrentHashMap线程安全吗

到底如何保证线程安全,你真的清楚吗?


欢迎拍砖,多多交流,转载请注明出处:[没长正的技术专栏](http://blog.meizhangzheng.com) 如涉及侵权问题,请发送邮件到xsj34567@163.com,如情况属实本人将会尽快删除。


上一篇 分布式

Comments

Content