博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java内存模型及线程案例分析
阅读量:2165 次
发布时间:2019-05-01

本文共 8829 字,大约阅读时间需要 29 分钟。

java 内存模型

学习目的

  • 了解更深层次内存的使用和读取实现,方便日后分析多线程内存相关问题
  • 工作中遇到的并发问题,并不好复现,需要对理论知识掌握得足够深刻,才能更好分析

操作系统内存模型

  • 现代CPU都存在多级缓存,用来缓存CPU经常使用的数据,提供数据的读写、处理速度,分为L1(高速缓存,保存极为常用的数据,容量小),L2(高速缓存,缓存经常使用的数据),Shared L3 Cache(共享缓存,线程间共享),主存(内存)
    现代CPU多级缓存
  • 其中L1,L2是CPU私有,能够命中CPU用到的数据 的80%,剩余的是L3缓存
    L1-L2命中率达80,极大提高CPU处理效率
  • Shared L3 Cache是共享缓存,多个CPU之间可以共享- 内存也是多个CPU共享

java内存模型

  • 每个线程都有自己的工作内存
  • 工作内存包含线程本地局部变量和主内存的副本拷贝
  • 线程间共享变量通过主内存在各线程间进行同步(通过去主内存去拷贝)
  • 内存模型图示
    java内存模型

线程间数据不一致问题

线程间不可见性

  • 由于线程间的不可见性,线程读取了其他未及时写入主内存的变量值
package com.myd.cn; /** *   * @author dymll  * * @date 2014/3/25  * * */ public class ThreadSharedVariables {	 private static int sharedVar = 0;		 public static void main(String[] args) {			 Thread threadA = new Thread(()->{			 	System.out.println(Thread.currentThread().getName()+" sharedVar = "+sharedVar);				 	sharedVar = 1;				 	System.out.println(Thread.currentThread().getName()+" sharedVar = "+sharedVar);						 },"ThreadA");						 	Thread threadB = new Thread(()->{	 		System.out.println(Thread.currentThread().getName()+" sharedVar = "+sharedVar);			 		sharedVar = 1;			 		System.out.println(Thread.currentThread().getName()+" sharedVar = "+sharedVar);					},"ThreadB");				 	threadA.start();		 	/ /线程B启动后,获取的值认为0,说明线程间共享变量还没等线程A在CPU缓存中修改后写入到主内存,就被线程B读取了,造成脏读		 	threadB.start();	 }}
  • 上述例子,线程间修改不可见导致线程发生脏读
    线程间不可见性发生脏读

jvm编译器进行指令优化,造成指令重排,发生变量赋值、读取顺序错误

  • jvm编译器或CPU编译对指令进行重排,发生读取先于赋值之前,造成数据错误
    package com.myd.cn;public class ThreadCommandReorder {	private static int a = 0;	private static int b = 0;		private static int x = 0;	private static int y = 0;		public static void main(String[] args) throws InterruptedException {			Thread threadA = new Thread(()->{				b = 1;				x = a;			},"threadA");					Thread threadB = new Thread(()->{				a = 1;				y = b;			},"threadB");					threadA.start();			threadB.start();					//等待ThreadA执行完后,主线程才往下继续执行			threadA.join();			//等待ThreadB执行完后,主线程才往下继续执行			threadB.join();					//此时因为JVM指令编译会对指令进行优化,可能会发生指令重排,而出现错误的赋值,如x = 1 ; y = 0			//合理输出应是 x=1,y=1			System.out.println("x = "+x+" ; y = "+y);	}}
  • 结果输出
    指令重排出现读取发生在赋值之前
  • 上述代码可能发生如下重排
    指令重排
  • 指令重排序发生在JVM编译器、CPU处理器中,两个阶段都可能会对指令进行重排,需要使用Happen-Before机制去避免可能出现的错误
    发生指令重排的阶段

Happen-Before解决指令重排问题

  • 程序次序规则,在程序中如操作A先于操作B发生,那么线程中操作A也先于操作B发生
  • 对象终结规则,一个对象的构造器的完成先行发生于finalize()或cleaner机制。
  • 锁规则,对于同一把锁,加锁操作先于释放锁操作
  • 传递规则,若操作A先行发生于操作B,而操作B有先行发生于C,则操作A先行发生于操作C
  • volatile变量规则,对一个volatile变量的写操作先行发生于对这个变量的读操作
  • 线程启动规则,Thread对象start()方法先行发生于此线程中的每个指令操作
  • 线程中断规则,一个线程对另一个线程调用interrupt()方法,先行发生于被中断线程检测到中断时间
  • 线程结束规则,线程中所有操作都先行发生于线程的终止,如线程结束,Thread.join()返回

synchronized和volatile关键字

  • synchronized ,java关键字,用来给方法或代码加锁,控制方法或代码块在同一时间点只有一个线程使用,用来解决多线程同时服务出现的并发问题
  • 如使用 synchronized方法保证卖票不会超卖
package com.myd.cn;import java.util.concurrent.Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class SynchronizedSellTicket {	private static int totalTickets = 10000;	private static final Object LOCK = new Object();		public static void main(String[] args) {			sellTickets();	}		/**	 * 通过启动线程池,向线程池提交任务,有线程池调度线程执行任务	 **/	private static void sellTickets(){			ExecutorService executorService = Executors.newFixedThreadPool(2);		Runnable runnableA = ()->{			i		nt threadAtickets = 0 ;					//如果不低于1张票					while (sellTicketsWithSynchronizedObject(1)) {				 		threadAtickets ++;					}					System.out.println("程序A卖出火车票: "+threadAtickets);			};					Runnable runnableB = ()->{					int threadBtickets = 0 ;					//如果不低于1张票					while (sellTicketsWithSynchronizedObject(1)) {						threadBtickets ++;					}					System.out.println("程序B卖出火车票: "+threadBtickets);			};						//提交两个卖票程序			executorService.submit(runnableA);			executorService.submit(runnableB);			}		/**	 * 在方法加锁,进行买票,保证票不卖超	 * @param count	  *  @return	 * */	 private static synchronized boolean sellTicketsWithSyncMethod(int count) {		 //如果要卖票数小于剩余票数,则不买,退出卖票流程 		if(totalTickets - count < 0){			r 		eturn false;		 		}else{			 		//否则进入卖票流程			 		totalTickets = totalTickets - count ;			 		return true;		 		}	 	}			 /**	  * 在对象加锁,降低锁的作用范围,提高并发量	  * * @param count	  * */	 private static boolean sellTicketsWithSynchronizedObject(int count) {	 	synchronized (LOCK) {				 	 //计算总票数减去申请票数的差值			 	 	 int flag = totalTickets - count;			 			 	  	//如果为负数,则表明总票数不够申请票数,不进行售卖			 	 	  	if(flag < 0){				 	  		 return false;			 	   	 }else{				 	     		totalTickets = totalTickets - count;				 	     		 return true;			 	      	 }		 	}   }}
  • 运行结果
    卖票不会卖超
  • 查看其字节码指令,可看出是通过synchronized机制完成线程间的变量共享
javap -c -p -v  SynchronizedSellTicket.class

sellTickets

synchronized关键字与volatile关键字

synchronized使用分类

  • synchronized方法

    • 方法使用ACC_SYNCHRONIZED表示,线程获取只有获取该标识,才能进入对应方法
    • 如果是static方法,锁是作用在类上,是串行执行,等获取锁的线程执行后,后续线程才能获取锁
    1.代码package com.myd.cn; public class CheckSynchronizedOnDiffObj { 	public static synchronized void staticSynchronized() throws InterruptedException{		System.out.println(Thread.currentThread().getName()+" is Sleeping");		Thread.sleep(1000L);		System.out.println(Thread.currentThread().getName()+" finished Sleeping");}			public static void main(String[] args) {		new Thread(()->{						try {							CheckSynchronizedOnDiffObj.staticSynchronized();					} catch (InterruptedException e) {			e.printStackTrace();		}					},"thread1").start();;								new Thread(()->{						try {			CheckSynchronizedOnDiffObj.staticSynchronized();			} catch (InterruptedException e) {			e.printStackTrace();							}						},"thread2").start();					}} 2.执行结果,串行执行的	thread1 is Sleeping	thread1 finished Sleeping	thread2 is Sleeping	thread2 finished Sleeping
  • 若是作用在实例方法,锁作用在类的实例上,类实例级别是可以同时执行

    package com.myd.cn; 	public class CheckSynchronizedOnDiffObj { 		public synchronized void ObjSysnchronized() throws InterruptedException {			System.out.println(Thread.currentThread().getName()+" is Sleeping");		Thread.sleep(1000L);		System.out.println(Thread.currentThread().getName()+" finished Sleeping");	}			public static void main(String[] args) {				CheckSynchronizedOnDiffObj check1  = new CheckSynchronizedOnDiffObj();				CheckSynchronizedOnDiffObj check2  = new CheckSynchronizedOnDiffObj();					new Thread(()->{						try {							check1.ObjSysnchronized();						} catch (InterruptedException e) {							e.printStackTrace();						}					},"thread1").start();;								new Thread(()->{						try {	 							check2.ObjSysnchronized();						} catch (InterruptedException e) {							e.printStackTrace();						}					},"thread2").start();						}} 2.结果,并行输出thread1 is Sleepingthread2 is Sleepingthread2 finished Sleepingthread1 finished Sleeping
  • synchronized代码块

    • 使用monitorenter和monitorexit指令控制线程进出同步代码块## synchronized与 ReentrantLock区别
    • 代码调用
    package com.myd.cn; import java.util.concurrent.locks.ReentrantLock; public class SynchronizedAndReentrantLock {	private static Object LOCK = new Object();		 private static void reetrant(){		synchronized (LOCK) {			 System.out.println("持有当前锁");			 synchronized(LOCK){				  System.out.println(" 再次持有锁");			  }		 }	 }		 /**	 * 使用默认非公平(可实现公平)的可重入排它锁	 * 可以添加获取锁的等待时间(超时不进行等待),也可不指定超时时间	 */	 private static void useReentrantLock(){		 ReentrantLock lock = new ReentrantLock();				 try {			 //获取后,一定在finally代码块进行释放			 lock.lock();			 System.out.println("持有当前锁");		 } catch (Exception e) {					 e.printStackTrace();		 }finally {			 lock.unlock();		 }	 }			 public static void main(String[] args) {		 	 //reetrant();			 useReentrantLock();	 }  }
    • 相同点
      1.都是用于多线程中对资源加锁,控制代码同一时间只有单线程在执行2.当一个线程获取了锁,其他线程均需要阻塞等待3.均为可重入锁
    • 差异点
    1.实现层次	Synchronzied是JVM级别实现的同步机制,由JVM字节码实现,以前并发量比较时,稍逊于ReentrantLock,但是随着JDK对其不断优化,一般场景下由于	ReentrantLockReentrantLock是代码层面实现的锁机制,获取可以设置超时时间,使用完毕后一定要释放。Synchronized无法设置超时时间,但是在获取锁后,使用完毕无需手动释放锁资源2.中断等待:	获取到Synchronized的线程如果因为外部原因(I/O,网络)等时阻塞,无法处理其他业务	Reentrant在进行中断等待时,可以处理其他业务,进而提高并发量,适用于高竞态的并发场景3.锁的公平性	ReentrantLock实现了非公平锁(默认)和公平锁(实例化传参为true)	synchronized 只有非公平锁

volatile

  • 用来保证多线间对变量的内存可见性,将变量强制写入到主存后,及时通知给其他线程
  • 使用Happen-Before机制避免对volatile相关对象前后的程序进行指令重排- 无法向synchronizedn那样,不能保证线程安全,不可用于数字的线程安全递增

volatile使用场景

  • 修饰状态变量,用于线程间访问该变量,保证个线程可以看到最小的内存值
package com.myd.cn.volatileDemo;  import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class VolatileSence {  //保证线程间共享变量的可见性,修改可以及时通知到其他线程   private volatile boolean isFinished = false;     public void finish(){    isFinished = true;    }      public void work(){     //当为true时,退出while循环      while (!isFinished) {        //working        System.out.println("i'm working");        }     }}
  • 单例模式下单实例对象构造,避免多线程情况下由于内存不可见而重复多次构造对象
package com.myd.cn.volatileDemo; public class Singleton {		//用时申请,减少开销,提供加载速度		private static volatile Singleton  singleton;		//私有化构造器		private Singleton(){		}		//使用double check,减少同步代码块的作用范围		public static Singleton getSingleton(){			if(null == singleton){				//同步机制作用在类上				synchronized (Singleton.class) {					if(null == singleton){						singleton = new Singleton();					}				}			}			return singleton;		}}

synchronzied和volatile区别

  • synchronized用于同部署控制,具有原子性,控制同一时间只有一个线程执行一个方法或代码块
  • volatile只保证线程的内存可见性,不具备锁的特性,无法保证修饰对象的原子性

转载地址:http://jhjzb.baihongyu.com/

你可能感兴趣的文章
【LEETCODE】36-Valid Sudoku
查看>>
【LEETCODE】205-Isomorphic Strings
查看>>
【LEETCODE】204-Count Primes
查看>>
【LEETCODE】228-Summary Ranges
查看>>
【LEETCODE】27-Remove Element
查看>>
【LEETCODE】66-Plus One
查看>>
【LEETCODE】26-Remove Duplicates from Sorted Array
查看>>
【LEETCODE】118-Pascal's Triangle
查看>>
【LEETCODE】119-Pascal's Triangle II
查看>>
【LEETCODE】88-Merge Sorted Array
查看>>
【LEETCODE】19-Remove Nth Node From End of List
查看>>
【LEETCODE】125-Valid Palindrome
查看>>
【LEETCODE】28-Implement strStr()
查看>>
【LEETCODE】6-ZigZag Conversion
查看>>
【LEETCODE】8-String to Integer (atoi)
查看>>
【LEETCODE】14-Longest Common Prefix
查看>>
【LEETCODE】38-Count and Say
查看>>
【LEETCODE】278-First Bad Version
查看>>
【LEETCODE】303-Range Sum Query - Immutable
查看>>
【LEETCODE】21-Merge Two Sorted Lists
查看>>