有一次去面试的时候,碰到这样一个问题,现在我有一个类Person,里面有set和get两个方法,现在我用多线程访问这两个方法,怎么保证线程安全。答:syn关键字,但是syn关键字的参数如果默认this的话,那么不能保证线程安全,传递Person才能保证。
首先,对于synchronized关键字,它的机制大概是这个样子的:synchronized以对象实例或者class对象为锁,如果synchronized修饰的是方法,首先锁默认class对象,想要访问这个方法,那么必须拿到这个锁,由于锁的唯一性(class对象的唯一性决定的),所以一次性只能有一个线程访问这个方法,vector使用的就是使用synchronized修饰了方法。如果synchronized修饰的是代码块,那么需要传递一个参数进去,如果参数传递this,那么锁的对象就是当前对象,如果传递的是其他对象实例,那么锁就是其他对象。锁的不同其实决定了线程的安全性。话不多说,上代码:

对于这一段代码而言,线程安全吗?vector本身是线程安全的,但是调用者不是。这段代码其实是在深入理解java虚拟机第二版的389页中,会出现以下的异常

虽然其出现的概率不高,但是会有这种问题存在。那么问题的本质在哪里呢,vector不是线程安全的吗?这里不详细叙述原理,但是问题在于i这个变量,这是发生错误的场景i<vector.size()判定通过,此时线程执行时间结束,保存当前状态切换,而此时,另一个线程刚好执行了remove,跳转回来以后,i的值是已经通过判定的,但是i其实已经过期了,所以此时会报错。对于不熟悉synchronized的人来说,他可能会这么写

但是其实这么写完全没有用(不仅仅没有解决当前问题,反而大大降低了程序执行的效率),问题出在哪里,锁对象。下面是正确的写法:

这段代码完美解决了线程安全的问题,接近或者达到了绝对线程安全的地步,但是效率比上面那段错误的代码还要低。其实还有一种锁机制ReentrantLock,这个就不在这里讲了。会另外出一篇博客。
其实最开始提出的问题还有一种解决方法,那就是用syn修饰Person的set()和get()方法,如果你还用vector并且用的很多,那么这种方法可以使用,如果你追求更好的效率,那么请直接舍弃这种方法,因为set和get中的操作一般都很简单,加入了锁那么这个类的操作效率会很低很低。
网友评论