上面的问题是一个很小的细节,不过我认为它是一个很关键的细节,在我做过对这个技术交流的人中我发现很多人其实对该处的知识大多都有错误的认识,而这种错误又常被人忽略结果导致对自己写出的线程程序有了错误的解读,每一个知识点都是结构严密的逻辑体,半天马虎就会把驴子当马用了,看起来没错其实差之千里了。
我在前面一直都强调synchronized关键字会给代码加锁,那么这个锁到底存在哪个地方啊,是方法还是对象还是类了?假如有面试官问你这个问题,你又当如何回答呢?
解决资源共享的锁在对象里(也在类里,这个我后面会提到就是不在方法上),每个对象都包含一个单一的锁,有的地方会把这个锁称为监视器,它本身也是对象的一部分,当该对象的任意一个带有synchronized关键字方法被调用的时候,对象都会被上锁,加锁的对象除了现在被调用的方法可以运行,其他所有带synchronized方法只有在对象释放掉锁后才能执行。所以,在java语言里一个对象所有带synchronized方法都是共用同一个锁。
讲了这么多估计还是有许多的童鞋感觉还是在云里雾里,我想换个角度解释解决冲突的问题,可能会开阔一下大家的思路。首先是共享的资源,也就是同一个时间很多线程会抢夺的资源到底是啥东东,共享的资源在我的理解里就是一块一堆线程都可以访问存储数据的内存区域,然而不同线程对这块内存区域的修改都是独立的,不会有交互,就像有一碗饭,大家排队轮流吃一口,可以前面吃完一口的的那个人不会告诉下一个人这碗饭吃了多少还剩多少,就算吃完了饭也只有当事人知道,其他人不知道,终于某个人吃完了最后一口,下一位又来吃,但是饭已经吃完了,没有饭了我们还说吃饭就不符合逻辑了,根据逻辑我们是希望在饭吃完时候大家都知道,大家就不用排队等饭吃了,线程冲突的问题就和这个类似,我们设定的约束条件该如何被执行了?在线程中到底谁是吃饭的人呢?根据我上面的代码,我是在对象的范畴里讨论资源冲突,对象里的一个方法就是吃饭的人,不同的方法就是不同的人了。有些人认为不同对象调用同一个方法去访问共享资源也会有冲突,大家看我上面写的实例代码,这种不是会有冲突的,为什么呢?其实java里的某一方法也是唯一的,这个不难理解,我们写的方法说白了就是一段代码,程序运行时候代码进入内存,内存的代码还是唯一的,因为我们就写了那一段,聪明的计算机不会肆意去copy里的代码,不同的对象执行同一个方法,这个方法在执行时候是唯一的,不可能同时有两个相同的方法在被调用,所以同一个方法是不会产生线程冲突的。但是不相同的方法调用共享资源就会产生冲突的问题了。
我们回到对象的锁,一个线程执行时候我们可以获得这个线程调用对象的锁多次,这个可能不太好理解,我举个例子:我们调用对象的一个方法,这个方法里又调用了对象的另一个方法,那么对象的锁就被调用两次了。Java虚拟机会记录下我们调用对象锁的次数了,一个对象的所有的锁都被解开了,那么锁的计数就为0了,如果对象调用了n次方法锁的计数就是n了。在程序中只有首先获得锁的那个线程才有机会获得多个锁的特权。
对象可以调用内部的属性和方法,构建对象的类也是可以调用属于类的属性和方法,那么类级别的操作也会存在线程冲突的问题,虽然属于类,但是解决方法和对象是一致的。
解决理论知识的讲解结束了,我们可以改写下前文里的代码了,代码如下:
package cn.com.sxia;
public class SynchronizedEvenGenerator implements Invariant {
private int i;
public synchronized void next(){
i++;
i++;
}
public synchronized int getValue(){
return i;
}
@Override
public InvariantState invariant() {
int val = getValue();
if (val % 2 == 0)
return new InvariantOK();
else
return new InvariantFailure(new Integer(val));
}
public static void main(String[] args) throws InterruptedException {
SynchronizedEvenGenerator gen = new SynchronizedEvenGenerator();
new InvariantWatcher(gen,4000);
while(true){
gen.next();
}
}
}
代码里我把所有的方法都加上了synchronized关键字了,有的童鞋会不会这样想过,我只加一个了,或者我加上自己认为要加的方法,我建议大家不要这么做,在一个类里要加就全加,有的方法不加,线程的随机调度将会成为最大的安全隐患了。对于我们写的监控程序就没必要加synchronized关键字了,我们需要它的随机,它的随机让我们随时掌控数据的变化。