目录

学习目标:

可见性介绍:

synchronized实现可见性原理:

synchronized实现可见性代码:

volatile实现可见性:

volatile不能保证原子性:

关于内存屏障:


 

学习目标:

浅谈Java多线程之内存可见性-LMLPHP

可见性介绍:

浅谈Java多线程之内存可见性-LMLPHP

浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP

synchronized实现可见性原理:

浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP

浅谈Java多线程之内存可见性-LMLPHP

优化之后更加符合处理器的特点

浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP

synchronized实现可见性代码:

先附上代码:

public class SynchronizedDemo {
	//共享变量
    private boolean ready = false;
    private int result = 0;
    private int number = 1;
    //写操作
    public void write(){
    	ready = true;	      				 //1.1
    	number = 2;		                    //1.2
    }
    //读操作
    public void read(){
    	if(ready){						     //2.1
    		result = number*3;	 	//2.2
    	}
    	System.out.println("result的值为:" + result);
    }

    //内部线程类
    private class ReadWriteThread extends Thread {
    	//根据构造方法中传入的flag参数,确定线程执行读操作还是写操作
    	private boolean flag;
    	public ReadWriteThread(boolean flag){
    		this.flag = flag;
    	}
        @Override
        public void run() {
        	if(flag){
        		//构造方法中传入true,执行写操作
        		write();
        	}else{
        		//构造方法中传入false,执行读操作
        		read();
        	}
        }
    }

    public static void main(String[] args)  {
    	SynchronizedDemo synDemo = new SynchronizedDemo();
    	//启动线程执行写操作
    	synDemo .new ReadWriteThread(true).start();
    	try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	//启动线程执行读操作
    	synDemo.new ReadWriteThread(false).start();
    }
}

 为什么共享变量可以不加static?

只要是对同一个对象的操作,多线程访问共享变量是不需要加static的。

这里是同一个外部类对象,然后外部类对象里面有2个内部类对象,相当于main里面的操作导致异步调用了read和write方法,这2个方法是都可以直接获取成员变量的。

同理:如果实现了runnable接口的对象,new了多个Thread,但是传入的是同一个实现了runnable接口的对象,那么共享变量是不需要加static的。

如果class piao extends Thread{

         private static int count = 10; // 如果这里不加static,那么这个变量就是各个线程独有的,不会共享
         public void run() {

                 .........
                if (count > 0) {
                    count--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,票还剩" + count);
                }
        }

...}

main方法里面{

        // 这里不是同一个对象,是多个对象

        Thread t1 = new piao();

        Thread t2 = new piao();

        Thread t3 = new piao();

        t1.start();

        t2.start();

        t3.start();

}

浅谈Java多线程之内存可见性-LMLPHP

浅谈Java多线程之内存可见性-LMLPHP

浅谈Java多线程之内存可见性-LMLPHP

2.1和2.2也是可以重排序的,虽然存在控制依赖关系,但是不存在数据依赖关系,只有存在数据依赖关系才不能重排序。这样执行结果有多种,就不一一列举了。

浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP

内存可见了,怎么还会执行结果不一致呢?保证了内存可见性并不能保证执行结果一致。这里read()操作和write()操作加了synchronized是原子性的,但是又不保证read()和write()哪个先执行,所以会出现2个结果,如果是先read()执行,那么result就是0,如果write()先执行,那么result就是6。最后通过延时保证write()先执行,结果就是只有6。

浅谈Java多线程之内存可见性-LMLPHP

加了synchronized,能够保证在主内存和工作内存及时的更新,保证了内存的可见性,但是不加synchronized,也可能内存可见,即工作内存和主内存的值能够更新,但是不能够保证,只是可能,因为编译器采取了优化,可能导致更新不及时。

 

volatile实现可见性:

浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP

volatile不能保证原子性:

浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP

使用ReentrantLock同步

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class VolatileDemo {

	private Lock lock = new ReentrantLock();
	private int number = 0;

	public int getNumber(){
		return this.number;
	}

	public void increase(){
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		lock.lock();
		try {
			this.number++;
		} finally {
			lock.unlock();
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final VolatileDemo volDemo = new VolatileDemo();
		for(int i = 0 ; i < 500 ; i++){
			new Thread(new Runnable() {

				@Override
				public void run() {
					volDemo.increase();
				}
			}).start();
		}

		//如果还有子线程在运行,主线程就让出CPU资源,
		//直到所有的子线程都运行完了,主线程再继续往下执行
		while(Thread.activeCount() > 1){
			Thread.yield();
		}

		System.out.println("number : " + volDemo.getNumber());
	}

}

浅谈Java多线程之内存可见性-LMLPHP

 

 

浅谈Java多线程之内存可见性-LMLPHP

浅谈Java多线程之内存可见性-LMLPHP浅谈Java多线程之内存可见性-LMLPHP

 

关于内存屏障:

为了实现volatile内存语义,JMM会分别限制编译器重排序和处理器重排序

浅谈Java多线程之内存可见性-LMLPHP

 

1.当第一个操作为普通的读或写时,如果第二个操作为volatile写,则编译器不能重排序这两个操作(1,3)

2.当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前(第二行)

3.当第一个操作是volatile写,第二个操作是volatile读时,不能重排序(3,2)

4.当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序(第三列)

 

内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
内存屏障可以被分为以下几种类型
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。        在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

       有的处理器的重排序规则较严,无需内存屏障也能很好的工作,Java编译器会在这种情况下不放置内存屏障。
       为了实现JSR-133的规定,Java编译器会这样使用内存屏障。

浅谈Java多线程之内存可见性-LMLPHP

 

     为了保证final字段的特殊语义,也会在下面的语句加入内存屏障。
     x.finalField = v; StoreStore; sharedRef = x;

 

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

JMM基于保守策略的JMM内存屏障插入策略:

1.在每个volatile写操作的前面插入一个StoreStore屏障

2.在每个volatile写操作的后面插入一个SotreLoad屏障

3.在每个volatile读操作的后面插入一个LoadLoad屏障

4.在每个volatile读操作的后面插入一个LoadStore屏障

 

浅谈Java多线程之内存可见性-LMLPHP

 

上图的StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了

因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存

 

浅谈Java多线程之内存可见性-LMLPHP

 

浅谈Java多线程之内存可见性-LMLPHP

x86处理器仅仅会对写-读操作做重排序

因此会省略掉读-读、读-写和写-写操作做重排序的内存屏障

在x86中,JMM仅需在volatile后面插入一个StoreLoad屏障即可正确实现volatile写-读的内存语义

这意味着在x86处理器中,volatile写的开销比volatile读的大,因为StoreLoad屏障开销比较大

浅谈Java多线程之内存可见性-LMLPHP

浅谈Java多线程之内存可见性-LMLPHP

 

========================Talk is cheap, show me the code=======================

10-07 19:44