Java多线程之synchronized与Lock

来源:互联网 发布:java将图片生成二维码 编辑:程序博客网 时间:2024/05/22 01:09

首先我们需要先理解一下什么是共享受限资源。

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不行。