我们来说说什么是互斥锁,互斥锁顾名思义就是相互排斥的锁,具体到我们Java语义就是加了这个锁之后同一个时刻只有一个线程执行这一块代码不管你是几个CPU,不管你多少线程,反正此时只能有一个线程执行!
基本上我们把需要互斥执行的那些代码称为临界区。
而我们的锁就是我们进入临界区的凭证!哪个线程获得锁就能单独的进去这个临界区在里面"为所欲为",而没得到锁的线程呢,你只能乖乖的在临界区外等着了!等到之前一个线程执行完毕了,释放了这个锁,才轮到你。
其实这个锁的本质实现就是:当一个线程获取到锁就是把它的线程id写入锁对象的对象头,来判断唯一。
所以当你有共享资源在多线程中相互竞争的时候你就能那这个互斥锁来保护你的共享资源,保证程序的正确性。
我们再来看看Synchronized,相信很多同学平日里也用过,Synchronized是Java中的关键字可以修饰方法或者代码块。
要注意一下如果我们的synchronized修饰的是静态方法,那表明锁的是当前的Class对象,如果修饰的不是静态方法,那表面锁的是当前的实例对象this。如果你用Synchronized(对象A),那当然你锁的就是这个对象A了。
这点其实会被很多同学忽略掉,我们上代码
看起来好像很完美,修改和获取方法都加了锁了。肯定线程安全了!
然而并不是的!
我们的静态方法加锁的代码其实就是等于synchronized(A.class);
而addOne()的加锁其实是synchronized(this)。所以两个方法就根本不是一个锁。那自然而然就保证不了安全了!
所以只有加的是同一个锁的时候才能保证有关联的共享资源(在我们代码中就是a),才会被正确的获取和修改!
所以改成这样就对了!
所以从上面我们可以得到一个结论,对一个资源的保护必须是同一个锁!但是呢如果是没有关联性的资源我们要细分粒度,比如以下代码
可以看到a和b没有任何的关联,这种情况下我们这样锁就导致了4个方法都互斥了!也就说同一时间4个方法只能有一个被执行!比如有个线程在addOneA,另一个线程在getB(),它竟然还得等addOneA结束。你看多捞啊!
所以呢在这种情况下我们需要细粒度的加锁!区分出没有关联的资源!
这样的话就能提升一下性能了!但是这样加锁我们的get和addOne是互斥的,在高并发的情况下就大大的不好了!性能太差了!所以一般情况下我们都CAS等一些其他手段去搞!还有注意细粒度锁可能会产生死锁!这里就不展开说了,之后我会专门搞一篇说说这些的。
是不是觉得差不多懂了?我们来看看下面的代码,经典的转账
锁也加了,看起来好像没问题?仔细想想看
有问题吧!这个锁是加在this上的!假设有A,B,C三个人,他们的余额分别是200,300, 400。这时候A要转账给B100,B要转给C100。
因为锁是加this上也就是实例对象上,而A,B,C就是3个实例对象所以他们各自加锁!假设A转B(线程1,此时的锁的是A.this)和B转C(线程2,此时锁的是B.this)是同时转的,你看两个锁不是同一个所以这两个线程能同时的进去transfer方法。那这情况就是B的钱要么是400,要么是200,反正怎么滴不会是300!
看完上面的我想你一定会对Synchronized有了深入的理解!希望对你有所帮助!