首先我们需要先理解一下什么是共享受限资源。
private int currentEvenValue = 0; public int next() { ++currentEvenValue;//++I表示先自增在赋值,I++表示先赋值在自增 //Thread.yield(); ++currentEvenValue; return currentEvenValue; }我们来看下这段代码,在多线程环境下,一个线程可能在另外一个线程刚刚执行完第一行++currentEvenValue的时候调用next方法,此时就会造成读取的currentEvenValue变量的值不正确,造成程序错误甚至崩溃,当然这个可能是一个小概率事件,但是根据墨菲定律,可能会出现的就一定会出现。大家可以自己测试下,为了更快的发现,我们可以使用Thread.yield()来快速发现失败。
为了解决这个冲突,Java内置了synchronized关键字来解决资源冲突。在Java中所有的对象都含有单一的锁(监视器),当在对象上调用任意声明为synchronized的方法的时候,,此对象都会被加锁,这时此对象的别的synchronized方法就只有等前一个方法再执行完毕并释放锁之后才能被调用。简单点来说,就是某个对象的所有synchronized方法共享同一个锁。
注意:在使用synchronized时要将域设置为private,否则synchronized无法阻止其他任务直接访问从而产生冲突。
一个任务可以多次获得对象的锁,如果一个方法在同一个对象上调用了另外的synchronized方法就会发生这种情况。jvm负责跟踪对象被枷锁的次数,在任务第一次给对象加锁的时候,计数为1,当这个任务再次获得锁时,计数会递增,每当任务离开一个synchronized方法计数会递减,当计数为0时锁被完全释放。每个类也有一个锁,作为类的Class对象的一部分,所以synchronized static方法可以再类的范围内防止对static数据的并发访问。
说了这么多,贴下对上述问题的解决代码:
private int currentEvenValue = 0; public synchronized int next() { ++currentEvenValue;//++I表示先自增在赋值,I++表示先赋值在自增 Thread.yield(); ++currentEvenValue; return currentEvenValue; }针对上一问题,我们也可以使用显式的Lock对象来加锁。
private Lock lock = new ReentrantLock(); public int anotherNext(){ lock.lock();//枷锁 try { ++currentEvenValue; Thread.yield(); ++currentEvenValue; return currentEvenValue; }finally{ lock.unlock();//释放锁 }在这里需要注意的需要注意的是如果需要继续调用lock方法,必须放置在带有unlock()的try-finally子句中,还有就是return必须在try子句中出现,以避免unlock不会过早发生。
看到这里我们不禁要问,synchronized与Lock的作用是一样的,那么他们俩到底有什么区别呢?
ReentranLock允许我们尝试获取但最终未获取到锁:
private ReentrantLock lock = new ReentrantLock(); public void unTimed(){ boolean captured = lock.tryLock(); try{ System.out.println("tryLock():"+captured); } finally { if(captured){ lock.unlock(); } } }还可以尝试着去获取锁,但是会在指定的时间单位后失败:
public void timed(){ boolean captured = false; try { captured = lock.tryLock(2, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } try{ System.out.println("tryLock(2, TimeUnit.SECONDS):"+captured); } finally { if(captured){ lock.unlock(); } } }显式的Lock对象在加锁与释放锁方面,相对于内建的synchronized锁来说,赋予了我们更细粒度的控制,我们根据需要来选择具体使用。有一点需要注意的是:Lock对象加锁所造成的阻塞可以被中断,而synchronized不行。