1、线程持有所和资源访问
public void methodA () throws InterruptedException{ synchronized (RA) { RC = true ; } } public void methodB() throws InterruptedException{ synchronized (RB) { RA = true ; } }
上述的methodA中,持有了RA的锁,这并不代表RA不能被其他线程所访问。RA一样是一个普通的资源,这个资源可以被任何资源访问。那么对RA加锁有什么用呢?
多线程同步块的锁,是用于限制对线程对该块的并发执行,只有持有该锁的线程,才有执行该代码块的权利。也可以说,这个锁时一个入门的钥匙,你需要想JVM获取到这个钥匙,你才能进来这个代码快的门。当执行完毕之后,会将钥匙归还给JVM。此时jvm才能将钥匙给另外一个人(线程),否则那个人会一直等待你吧钥匙归还。
但是RA作为一个资源,在不同的代码快中使用它是完全没有限制的,只要不是同一个代码块中,就可以多线程访问。所以methodB和methodA都会并发访问资源RA。
2、使用对象锁
1中描述的例子,RA和RB的定义分别如下:
private Boolean RA = false ;
private Boolean RB = false ;
当程序并发访问methodA和methodB时,你会发现,methodB需要等待methodA执行完之后才能执行。为什么会这样?他们明明是不同的锁??
问题出现在变量的赋值上。Boolean虽然是对象,但是在初始化时是使用基本类型的值false赋值的,false和true作为基本变量值,是存储在jvm的栈内存中,这里先说说栈:
栈 的优势
存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量( int, short, long, byte, float, double, boolean, char )和对象引用。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。 假设我们同时定义:
int a = 3;
int b = 3 ;
编译器先处理 int a = 3 ;首先它会在栈中创建一个变量为 a 的引用,然后查找栈中是否有 3 这个值,如果没找到,就将 3 存放进来,然后将 a 指向 3 。接着处理 int b = 3 ;在创建完 b 的引用变量后,因为在栈中已经有 3 这个值,便将 b 直接指向 3 。这样,就出现了 a 与 b 同时均指向 3 的情况。这时,如果再令 a=4 ;那么编译器会重新搜索栈中是否有 4 值,如果没有,则将 4 存放进来,并令 a 指向 4 ;如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况 a 的修改并不会影响到 b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
从上述的描述中,我们可以知道,RA和RB其实都是指向栈内存中的false对象。如果将RB初始化为true,那么就可以对methodA和methodB进行并发访问。但是其实这样是很危险的,因为false有可能也被其他线程访问。那么就会出现等待了。
所以不要使用基本类型作为线程锁。
引申:
下面主要介绍 JAVA 中的堆、 栈 和 常量池 :
1. 寄存器
最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制。
2. 栈
存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
3. 堆
存放所有new出来的对象。
4. 静态域
存放静态成员(static定义的)
5. 常量池
存放字符串常量和基本类型常量(public static final)。
6. 非RAM存储
硬盘等永久存储空间
这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。
对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
参考资料:http://www.cnblogs.com/luckyliu/archive/2012/04/05/2433657.html