【Java】多线程锁

Lock

Lock提供了与synchronized类似的同步功能,只是在显式的获取和释放锁,因此有了锁获取和释放的可操作性、可中断的获取锁以及超时获取锁等多种同步特性。

代码实例:

Lock lock = new ReentrantLock();
lock.lock();
try{
}finally{
    lock.unlock();
}

特性

  • 尝试非阻塞地获取锁:当前线程尝试获取锁,如果这一时刻锁没有被其它线程获取到,则成功获取并持有锁;
  • 能被中断地获取锁:获取到的锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会抛出,同时锁被释放;
  • 超时获取锁:在指定时间后获取锁,如果时间到了无法获取,则返回

队列同步器

队列同步器是用来构建锁或其它同步组件的基础框架
它使用一个int变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作

重写同步器指定方法是,需要使用以下3个方法访问或修改同步状态

  • getState():获取当前同步状态
  • setState(int newState):设置当前同步状态
  • compareAndSetState(int expect, int update):使用CAS设置当前状态,保证状态设置原子性

重入锁

ReentrantLock:支持重进入的锁,可以支持一个线程对资源的重复加锁,同时还支持获取锁时的公平和非公平性选择。

重进入指任意线程在获取锁之后能够再次获取该锁而不会被锁所阻塞,主要解决以下两个问题:

  • 线程再次获取锁:如果获取锁的线程为当前占据锁的线程,则再次获取成功;
  • 锁的最终释放:在第n次释放该锁后,其他线程能够获取该锁;

公平是通过判断同步队列中是否有前驱节点来判断线程请求锁的早晚,从而让最早请求锁的线程获取锁。
公平性锁保证了FIFO原则,但是代价是造成了大量的线程切换。非公平性锁可能造成一个线程频繁可以获取锁,使其它线程“饥饿”,但是这样会减少线程切换,保证了吞吐量。

读写锁

读写锁维护了一对锁,一个读锁,一个写锁。当写锁被获取到时,后续的读写操作都会被阻塞,写锁释放后,所有操作继续执行。

特性

  • 公平性选择:支持非公平和公平的锁获取方式;
  • 重进入:支持重进入,读线程可以再次获取锁,写线程可以再次获取写锁,同时可以获取读锁;
  • 锁降级:遵循获取写锁、获取读锁、释放写锁的次序

代码实例

static Map<String,Object>map = new HashMap<String,Object>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();

public static final Object get(String key){
    r.lock();
    try{
        return map.get(key);
    }finally{
        r.unlock();
    }
}

public static final Object put(String key, Object value){
    w.lock();
    try{
        return map.put(key,value);
    }finally{
        w.unlock
    }
}

public static final void clear(){
    w.lock();
    try{
        map.clear();
    }finally{
        w.unlock();
    }
}

Condition接口

Condition接口提供了类似Object的监视器方法
Condition对象由Lock对象创建
Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁

代码实例

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

//当前线程进入等待状态直到被通知或中断
public void conditionWait() throws InterruptedException{
    lock.lock();
    try{
        condition.await();
    }finally{
        lock.unlock();
    }
}

//唤醒一个等待在Condition上的线程
public void conditionSignal() throws InterruptedException{
    lock.lock();
    try{
        condition.signal();
    }finally{
        lock.unlock();
    }
}

有界队列实例

public class BoundedQueue<T>{
    private Object[] items;
    //添加下标、删除下标、数组当前数量
    private int addIndex, removeIndex, count;
    private Lock lock = new ReentrantLock();
    private Condition notEmpty = lock.newCondition();
    private Condition notFull = lock.newCondition();

    public BoundedQueue(int size){
        items = new Object[size];
    }

    //添加元素,如果数组满,则该线程进入等待状态,直到有空位
    public void add(T t) throws InterruptedException{
        lock.lock();
        try{
            while(count == items.length){
                notFull.await();
            }
            items[addIndex] = t;
            if(++addIndex == items.length){ 
                addIndex = 0; 
            }
            ++count;
            notEmpty.signal();
        }finally{
            lock.unlock();
        }
    }

    //删除元素,如果数组空,则该线程进入等待状态,直到有新添加元素
    @SuppressWarnings("unchecked")
    public T remove() throws InterruptedException{
        lock.lock();
        try{
            while(count == 0){
                notEmpty.await();
            }
            Object x = items[removeIndex];
            if(++removeIndex == items.length){
                removeIndex = 0;
            }
            --count;
            notFull.signal();
            return (T)x;
        }finally{
            lock.unlock();
        }
    }
}

小结

Lock和synchronized比较而言,Lock能完成synchronized所实现的所有功能;
但是Lock有比synchronized更精确的线程语义和更好的性能;
synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

猜你喜欢

转载自blog.csdn.net/Francis123580/article/details/80589502