从Happens-before 原则说volatile和synchronized

什么是Happens-before ?

1.)Happens-before 规则是用来解决可见性问题的:在时间上,动作A发生在动作B之前,B保证能看见A,这就是Happens-before 原则。
2.)如果一个操作Happens-before 第二个操作,那么我们就说第一个操作对于第二个操作是可见的

什么不是Happens-before ?

两个线程没有相互配合的机制,所以代码X和Y的执行结果并不能保证总被对方看到的,这就是不具备happens-before。

happens-before规则有哪些?

1.)单线程规则
后面的语句一定能看见前面的代码的操作
2.)锁操作
A线程解锁了,B线程获得了同样的锁,那么B一定能看到A的所有操作
3.)volatile变量
多线程之间读取到volatile变量的所有操作。且与volatile变量有关的变量也能保证可见,volatile写入之前的操作,其余变量的可见。
4.)线程启动
子线程能看见主线程的所有操作
5.)线程的join
在B中执行A.join();A happens-before B 同4
6.) 具有传递性
hb(AB),hb(B,C),那么A hb C
7.)中断
一个线程被中断,其他线程一定能感知
8.) 并发工具类的happens-before 原则
a. 线程安全的容器get一定能看见在此之前的put操作。
b.CountDownLatch
c.Semaphore 获取许可证之前必须有人释放
d.CyclicBarrier 必须达到释放的条件
e. future get方法一定是拿到执行完后的结果
f.线程池中提交的任务,每一个任务都可以看到提前之前的所有结果。

volatile关键字

volatile 是一种比锁更加轻量的同步操作,仅仅是刷回主内存这个操作,不会发生上下文切换这种开销。如果一个变量被volatile修饰,JVM便会知道这个变量是可能被并发修改的,会进行一系列操作,比如进制重排序等等。
volatile开销小,责任也小,volatile做不大锁的原子性。

不适用的场景 a++

/**
 * @author FuYouJ
 * @date 2020-02-15 11:52
 * volatile 不适合A++
 */
public class NoVolatile implements Runnable {
   volatile int a=0;
   AtomicInteger realA=new AtomicInteger();
    @Override
    public void run() {
        for (int i = 0; i <10000 ; i++) {
            a++;
            realA.incrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Runnable r=new NoVolatile();
        Thread thread1=new Thread(r);
        Thread thread2=new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread1.join();
        //小于200000
        System.out.println("a="+((NoVolatile)r).a);
        System.out.println("realA="+((NoVolatile)r).realA);
    }
}

在这里插入图片描述

适合的场景1

类似于boolean flag 单纯的赋值操作不依赖原先的值(不适合消费者生产者模式)
这样的代码是可行的

flag=true;
a=6;

这样的代码是不可行的

flag=!flag;
a=a+1;

boolean的复制并不是一般的先读取,再改写,再回写3步操作,而是原子性的1步操作。

适合的场景2 作为刷新之前变量的触发器

那么以下代码在执行的时候,是所有线程可见的。

int a=1;
int ab=2;
volatile b=3;

总结一下,volatile的两点作用:
1.可见性:
读取一个变量之前,需要先使本地的缓存失效,这样就必须到主内存读取最新值,写一个volatile属性,会立即刷回到主内存。
2.禁止指令重排序:解决单例模式双重锁的乱序问题

volatile 和synchronized的关系?

如果一个共享变量只是被各个线程单纯的赋值,而没有其他的操作,那么这个时候局可以用volatile代替synchronized或者代替原子变量,因为赋值一步操作是具有原子性的,而volatile又保证了可见性,所以足以保证线程安全而不必要花费太大的开销(上下文切换和获取锁等等)。

volatile 可以让long double的赋值是原子性的

由于long和double是64位的值,单个数字的写入会被视为两个单独的写入,每次写32位,这可能会导致A线程读取的是前32位(改)后32位(未修改),B线程读取的是前32位(改)后32位(改)。
官方鼓励JVM(32位),不必要拆分64位写操作,鼓励程序员(我可能是)用volatile修饰变量。或者使用额外的同步

synchronized 的可见性

synchronized 不仅仅有保证原子性,还保证可见性。
例如用 syncronized代码块执行a++却不用volatile修饰,同样可以保证a对其他线程可见。

int a=0;
synchronized (this){
 for(;;){
 a++;}
 }

符合上面的原则2,A线程解锁了,B线程获得了同样的锁,那么B一定能看到A的所有操作。

//定义变量
int a,b,c,d;
//线程A执行
void change(){
a=2;
b=3;
c=4;
 synchronizedthis{
	 d=5;
  }
}
//线程B执行
void read(){
	synchronzied(this){int aa=a}
	bb=b;
	cc=c;
	dd=d;
}
发布了11 篇原创文章 · 获赞 1 · 访问量 608

猜你喜欢

转载自blog.csdn.net/Fujie1997/article/details/104324827