您的位置:首页 > 编程语言 > Java开发

JAVA学习笔记--4.多线程编程 part2.多线程关键字

2014-01-09 16:54 417 查看

多线程关键字

synchronized

首先从我们的日常生活讲起。假设一个教室由N个座位,每个同学只能占一个座位。当N+M个同学来占座时,则M个同学需要等待。座位即资源,同学即线程。占座可以理解为占用资源,在代码中常用lock(acquire)表示,如果没有座位,我就在教室外面等待,出于阻塞状态(blocked);离开座位可以理解为释放所占用的资源,在代码中常用unlock(release)表示。当某同学离开座位时,需要该同学发出一个信号,通知外面等待的同学进来占座。

上面的例子实际上对应的是JAVA里面的Semaphore类;而synchronized则是Semaphore的一个特例,即上面的座位数N=1。 synchronized 表达的语义表达的有3个:

原子:即通过互斥保证同时只有一个线程能够获取锁,保证数据的原子性

内存可见:即离开同步块后,临界区内的操作都已经刷新到内存里面

有序:同步块里面的代码块内部执行可以乱序,但是必须保证进入同步块和离开同步块的代码不能被乱序执行。

从锁的实现原理来看,底层必须存在某个共享变量,所有线程在争夺锁资源时,必须首先查看该共享变量的状态。
基于这种前提,就能够很好解释为什么使用synchronized(lockObject)这个block块时,必须要指定lockObject这个对象。HotSpot虚拟机在实现锁时,把锁的状态放在Java对象的头部Mark Word。详见聊聊并发(二)——Java SE1.6中的Synchronized。同时,注意在Linux的多线程库函数时,比如
int pthread_mutex_lock(pthread_mutex_t* mutex);
也是对某个内存地址进行锁定,都是锁定某个内存地址。

voliatile

把变量声明为volatile类型后,编译器和运行时都会注意到这个变量是共享的,因此不会将该变量上的操作和其他内存操作进行重排序.volatile变量不会被缓存在寄存器或者处理器其他不可见的地方,因此在读取volatile类型的变量总是会返回最新写入的值

对volatile的变量读写操作可以分别理解为synchronized get,set方法.但是区别是读写volatile变量不会执行加锁操作.所以说,volatile变量是一种比synchronized关键字更轻量级的一种同步。由于没有涉及到锁操作,声明volatile字段很可能比使用同步的开销更低,至少不会更高。但如果在方法内频繁访问volatile字段,很可能导致更低的性能,这时还不如锁住整个方法。但是对volatile字段进行“++”这样的读写操作是非原子操作,相当于read,modify和write操作。

另外,有序性和可见性仅对volatile字段进行一次读取或更新操作起作用。声明一个引用变量为volatile,不能保证通过该引用变量访问到的非volatile变量的可见性。同理,声明一个数组变量为volatile不能确保数组内元素的可见性。volatile的特性不能在数组内传递,因为数组里的元素不能被声明为volatile。

volatile 变量通常用作表示某个操作完成,发生中断或者状态的标志;对volatile变量的写入操作应该不依赖当前volatile变量的值.

一点小历史

在旧的JMM下,访问volatile变量不能被重排序,但是,它们可能和访问非volatile变量一起被重排序。这破坏了volatile字段从一个线程到另外一个线程作为一个信号条件的手段。

在新的JMM下,除了volatile变量不能彼此重排序,还新增了volatile周围的普通字段的也不再能够随便的重排序了。写入一个volatile字段和释放监视器有相同的内存影响,而且读取volatile字段和获取监视器也有相同的内存影响。事实上,因为新的内存模型在重排序volatile字段访问上面和其他字段(volatile或者非volatile)访问上面有了更严格的约束。当线程A写入一个volatile字段f的时候,如果线程B读取f的话 ,那么对线程A可见的任何东西都变得对线程B可见了。

需要值的一提的是,在C/C++中,volatile 没有禁止重排序这个语义,但是在Java中却有禁止重排序这个语义。详细的介绍见@何_登成的C/C++ Volatile关键词深度剖析

final

在仔细阅读深入理解Java内存模型(六)——final,尝试总结了下。

对final域,编译器和处理器要遵守两个重排序规则:

1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

用大白话表达的话,就是说

在创建某个对象时,如果该对象含有final属性,那么这个final属性值必须在构造方法内完成初始化赋值,然后才能给外部对象引用。写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障。

读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。

对于引用类型,写final域的重排序规则对编译器和处理器增加了如下约束:在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

看完上面的话,心理还有不踏实。顾名思义,final表示“最终的”,也就是不可变。在这里就是说final的值会被正确赋值上,不会被构造泄露;同时一旦被赋值后,各个线程将看到相同的值,且这个值不会再被改变。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: