Lock接口

Lock与synchronized

  Lock和synchronized在功能上是一样的。不过Lock提供了一些其他功能,包括定时的锁等待、可中断的锁等待、公平性,以及实现非块结构的加锁

从性能上Lock的实现类ReentrantLock在JDK5.0之前要好于synchronized,在JDK6.0之后,synchronized做了优化,所以两者的性能相差无几了。

那在使用上应该选择哪个呢?在《Java并发编程实战》中有一句话:"仅当内置锁不能满足需求时,才可以考虑使用ReentrantLock"。在一些内置锁无法

满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可

中断的锁获取操作,公平队列,以及非块结构的锁。否则,还是应该优先使用synchronized。

  在JDK5.0中,内置锁与ReentrantLock相比还有另外一个优点:在线程转储中能给出在哪些调用帧中获得了哪些锁,并能够检测出发生死锁的线程。JVM

并不知道哪些线程持有ReentrantLock,因此在调试使用ReentrantLock的线程问题时,将起不到帮助作用。JDK6.0解决了这个问题,它提供了一个管理和调试

接口,锁可以通过该接口进行注册,从而与ReentrantLock相关的加锁信息就能出现在线程转储中,并通过其他的管理接口和调试接口来访问。

  在内置锁中,死锁是一个很严重的问题,恢复程序唯一的方法是重新启动程序,而防止死锁的唯一方法就是在构造程序时避免出现不一致的锁顺序。

ReentrantLock有可定时与可轮询的功能,这就从另一个方面避免了死锁的发生(注意,使用ReentrantLock一定要在finally中释放锁)。

Lock的实现类ReentrantLock(重入锁)

  重入锁ReentrantLock,顾名思义,就是支持重进入的锁(synchronized隐式的支持重进入),它表示该锁能够支持一个线程对资源的重复加锁。除此之外,

该锁还支持获取锁时的公平和非公平性选择。

  1、ReentrantLock的基本使用。

package org.burning.sport.javase.thread.reentrantlock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest implements Runnable{
    private static Lock lock = new ReentrantLock();
    private int i;

    @Override
    public void run() {
        while(true) {
            increment();
        }
    }

    public void increment() {
        try {
            lock.lock();
            i++;
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            //一定要在finally这里解锁,否则就是定时炸弹
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockTest test = new ReentrantLockTest();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        Thread t3 = new Thread(test);

        t1.setName("线程一:");
        t2.setName("线程二:");
        t3.setName("线程三:");

        t1.start();
        t2.start();
        t3.start();
    }
}

   2、锁的公平性

    如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。

  public ReentrantLock(boolean fair); 默认是非公平的。公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换影响性能,非公平的锁则会造成线程的 “饥饿”。

  3、Lock的中断响应

    线程的中断 中可以看到,Lock是可以响应线程触发的中断的。只要在获取锁的时候用 lock.lockInterruptibly();就可以了。

  4、轮询锁与定时锁

    lock.tryLock();  和 lock.tryLock(5, TimeUtil.SECONDS);  在内置锁中,死锁是一个很严重的问题,恢复程序唯一的方法是重新启动程序,而防止死锁的唯一方法就是在

  构造程序时避免出现不一致的锁顺序。ReentrantLock有可定时与可轮询的功能,这就从另一个方面避免了死锁的发生(注意,使用ReentrantLock一定要在finally中释放锁)。 

public class TryLockTest implements Runnable {
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                lock.lock();
                //do something .....
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

   5、ReentrantLock的好搭档Condition条件

    Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的new Condition()方法)

  创建出来的,换句话说,Condition是依赖Lock对象的。

  示例代码:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionUseCase {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void conditionWait() throws InterruptedException {
        lock.lock();
        try {
            condition.await();
        } finally {
            lock.unlock();
        }
    }

    public void conditionSignal() throws InterruptedException {
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}

   Condition的(部分)方法及描述:

  void await() throws InterruptedException   

  当前线程进入等待状态直到被通知(signal)或中断,当前线程将进入运行状态且从await()方法返回的情况,包括:

  其他线程调用该Condition的signal()或signalAll()方法,而当前线程被选中唤醒

     □ 其他线程(调用interrupt()方法)中断当前线程;

  □ 如果当前等待线程从await()方法返回,那么表明该线程已经获取了Condition对象所对应的锁;

       void awaitUninterruptibly() 

  当前线程进入等待状态直到被通知,从方法名称上可以看出该方法对中断不敏感

  long awaitNanos(long nanosTimeout) throws InterruptedException 

  当前线程进入等待状态直到被通知、中断或者超时。返回值表示剩余的时间,如果在nanosTimeout纳秒

    之前被唤醒,那么返回值就是(nanosTimeout - 实际耗时) ,如果返回值是0或者负数,那么可以认定已经超时了

  boolean awaitUntil(Date deadline) throws InterruptedException 

  当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定时间就被通知,方法返回true,否则,

  表示到了指定时间,方法返回false

    void signal()  唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁

  void signalAll()  唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须获得与Condition相关联的锁。

   示例代码:

  https://gitee.com/play-happy/base-project/blob/developer/src/main/java/org/burning/sport/javase/thread/reentrantlock/condition/WaxOmatic.java

线程阻塞工具类:LockSupport

  LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,它弥补了由于resume()在前发生,

  导致线程无法记录执行的情况。和Object.wait()相比,它不需要先获得某个对象的锁,也不会抛出InterruptedException异常。

  LockSupport提供的阻塞和唤醒的方法

  void park()   阻塞当前线程,如果调用 unpark(Thread thread) 方法或者当前线程被中断,才能从park方法返回

  void parkNanos(long nanos)  阻塞当前线程,最长不超过nonos纳秒,返回条件在park()的基础上增加了超时返回

  void parkUntil(long deadline)  阻塞当前线程,知道deadline时间(从1970年开始到deadline时间的毫秒数)

  void unpark(Thead thread)  唤醒处于阻塞状态的线程Thread

  LockSupport sample demo:

package org.burning.sport.javase.thread.reentrantlock.lock.support;

import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    private static Object u = new Object();
    static ChangeObjectThread thread1 = new ChangeObjectThread("t1");
    static ChangeObjectThread thread2 = new ChangeObjectThread("t2");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String threadName) {
            super.setName(threadName);
        }

        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        thread1.start();
        Thread.sleep(100);
        thread2.start();
        LockSupport.unpark(thread1);
        LockSupport.unpark(thread2);
        thread1.join();
        thread2.join();

    }
}

读写锁 ReentrantReadWriteLock

  1、使用示例:

package org.burning.sport.javase.thread.readwritelock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Cache {
    static Map<String, String> map = new HashMap<>();
    static ReadWriteLock lock = new ReentrantReadWriteLock();
    static Lock readLock = lock.readLock();
    static Lock writeLock = lock.writeLock();

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

    public static final void put(String key, String value) {
        writeLock.lock();
        try {
            map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

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

   2、读写锁的实现分析:

  2.1、读写状态设计

    读写锁同样依赖自定义同步器来实现同步功能。而读写状态就是其同步器的同步状态。同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在

  同步状态(一个整形变量)上维护多个读线程和一个写线程的状态。如果在一个整形变量上维护多种状态,就一定需要 “按位切割使用” 这个变量,读写锁将变量切分为了两

  个部分,高16位表示读,低16位表示写。

  2.2、写锁的获取与释放

    写锁是一个支持重入的排它锁。如果当前线程已经获取了写锁,则增加写状态。写锁的释放与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态

  为0时表示写锁已被释放。

  2.3、读锁的获取与释放

    读锁是一个支持重入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问(或者写状态为0)时,读锁总会被成功的获取,而所做的也只(线程安全的)是增加读

  状态。读锁的每次释放均减少读状态,减少的值是(1<<16)。

  2.4、锁降级

    锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有)写锁,

  再获取到读锁,随后释放写锁的过程。(ReentrantReadWriteLock不支持锁升级)

参考:

  【1】《Java并发编程的艺术》,方腾飞

  【2】《Java并发编程实战》,童云兰

  【3】《Java高并发程序设计》,葛一鸣

  【4】《Think In Java》,第21章 并发

 

    

 

posted @ 2018-11-09 22:27  寻找风口的猪  阅读(307)  评论(0编辑  收藏  举报