大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
在谈及线程安全时,常会说到一个变量——volatile。在《Java并发编程实战》一书中是这么定义volatile的——“Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程”。这句话说明了两点:①volatile变量是一种同步机制;②volatile能够确保可见性。这两点和我们探讨“volatile变量是否能够保证线程安全性”息息相关。
成都创新互联公司专注于邓州网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供邓州营销型网站建设,邓州网站制作、邓州网页设计、邓州网站官网定制、小程序定制开发服务,打造邓州网络公司原创品牌,更为您提供邓州网站排名全网营销落地服务。
什么是同步机制?在并发程序设计中,各进程对公共变量的访问必须加以制约,这种制约称为同步。也就是说,同步机制即为对共享资源的一种制约。那么问题来了:volatile这种“稍弱的同步机制”是怎么制约各个进程对共享资源的访问的呢?答案就在“volatile能够确保可见性”中。
2.1 可见性
volatile能够保证字段的可见性:volatile变量,用来确保将变量的更新操作通知到其他线程。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
可见性和“线程如何对变量进行操作(取值、赋值等)”有关系:
我们要先明确一个定律:线程对变量的所有操作(取值、赋值等)都必须在工作内存(各线程独立拥有)中进行,而不能直接读写内存中的变量,各工作内存间也不能相互访问。对于volatile变量来说,由于它特殊的操作顺序性规定,看起来如同操作主内存一般,但实际上 volatile变量也是遵循这一定律的。
关于主存与工作内存之间具体的交互协议(即一个变量如何从主存拷贝到工作内存、如何从工作内存同步到主存等实现细节),Java内存模型中定义了以下八种操作来完成:
lock:(锁定),unlock(解锁),read(读取),load(载入),use(试用), assign(赋值),store(存储),write(写入)。
volatile 对这八种操作有着两个特殊的限定,正因为有这些限定才让volatile修饰的变量有可见性以及可以禁止指令重排序 :
① use动作之前必须要有read和load动作, 这三个动作必须是连续出现的。【表示:每次工作内存要使用volatile变量之前必须去主存中拿取最新的volatile变量】
② assign动作之后必须跟着store和write动作,这三个动作必须是连续出现的。【表示: 每次工作内存改变了volatile变量的值,就必须把该值写回到主存中】
有以上两条规则就能保证每个线程每次去拿volatile变量的时候,那个变量肯定是最新的, 其实也就相当于好多个线程用的是同一个内存,无工作内存和主存之分。而操作没有用volatile修饰的变量则不能保证每次都能获取到最新的变量值。
2.2 所以volatile究竟能否保证线程安全性?
不能。
通过2.1,我们已经很明确在多线程环境下,一个线程修改了用volatile修饰的变量后,其他线程能够立刻读取到该变量的最新值。但是,volatile并不能保证各个线程是串行去访问同一变量的,在机器是多核的情况下,两个或多个线程同时对同一共享变量做修改,依旧会出现线程安全问题。例如:机器是多核的情况下,i == 0,两个线程同时操作 i++,最终的结果就有可能出现错误的结果“ i == 1”。
所以:volatile不能保证线程安全性,因为要保证线程安全性就得保证是以串行形式来访问操作共享资源的,而volatile做不到这点。
2.3 通过代码来验证“即使变量用了volatile来修饰,依旧会出现线程安全问题”
测试结果:(1)出现了大量的重复数字; (2)最后还输出了 “-1”;==》说明变量即使用volatile修饰了但依旧出现了线程安全问题。
代码解析:
出现问题(1)的原因:线程存在“先检查后执行”的竞态条件。可能有两个线程同时拥有CPU的执行权(机器是双核的),它们判断到做“if (ticket > 0)”,并同时做“ticket--”操作。
出现问题(2)的原因:
①当ticket==1时,两个或多个线程同时通过了“if (ticket > 0)”的判断,并进入了判断框中去执行代码;
②然后它们执行到“Thread.sleep(100);”就睡了;
③睡醒后总有一个线程会先抢到cup的执行权,然后执行“ticket--”操作,并将最新的ticket数值推送告知到每个线程;
④此时那些在判断框中的其他的线程并不会再次做“if (ticket > 0)”的判断,而是直接拿最新的ticket并做“ticket--”操作。
就算线程在“ticket--”之前每次都做“if (ticket > 0)”的判断,也依旧会有线程安全问题,因为又可能出现①那种同时通过判断的状态。
总结:volatile只能确保可见性和防止字段重排序(防止字段重排序本文中没做深入讨论),不能保证线程安全性。