Java高级技术第五章——高并发之Lock及Condition

前言

前言点击此处查看:
http://blog.csdn.net/wang7807564/article/details/79113195

ReentrantLock

使用ReentrantLock可以完成synchronized同步的功能,需要注意的是,必须要手动释放锁
这个类封装在java.util.concurrent.locks中,是lock接口的唯一实现。
lock和synchronized的区别

  1. Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
  2. Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  3. 使用synchronized锁定的话如果遇到异常,JVM会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放。
  4. Synchronized是非公平锁,谁抢到就归谁,这样效率比较高,而ReentrantLock可以指定为公平锁,也就是大家依次在队列中等待,先等待锁的先获得锁。

使用Lock:

Lock lock = new ReentrantLock();

虽然锁本身不抛出异常,但是使用lock锁定的线程常常在try-catch-finally中来完成,也是为了确保在finally中手动将锁释放:

                try {

                        lock.lock(); //synchronized(this)

/*代码块*/

                } catch (InterruptedException e) {

                        e.printStackTrace();

//异常处理

                } finally {

                        lock.unlock();

//要手动去释放锁

                }

在多个线程中都使用这样一种加锁的方法,就可以实现线程的同步,实现与synchronized相同的功能。

尝试锁定:

使用reentrantlock可以进行“尝试锁定”tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。
使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行
可以根据tryLock的返回值来判定是否锁定,也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中,例如:

Lock lock = new ReentrantLock();

boolean locked = lock.tryLock();

//判断是否已经被锁定了。

If(locked) lock.unlock();

//如果被锁定了,释放锁。

在try-catch-finally中:

                try {

                        locked = lock.tryLock(5, TimeUnit.SECONDS);//延迟5秒判断,但不是阻塞的

                        /*执行代码块*/

                } catch (InterruptedException e) {

                        e.printStackTrace();

                } finally {

                        if(locked) lock.unlock();//要在finaly中手动释放锁。

                }

使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断。

Lock lock = new ReentrantLock();
Thread t= new Thread(()->{
                        try {
                                lock.lock();
                                lock.lockInterruptibly(); //可以对interrupt()方法做出响应
/*代码块*/
                        } catch (InterruptedException e) {
                                System.out.println("interrupted!");
//这里就是响应interrupt()方法的代码区
                        } finally {
                                lock.unlock();
//最后,还需要释放一下锁,注意有可能没有获得锁,然后调用unlock()造成的异常。
                        }
                });
t.start();//启动线程

如果想要打断线程,执行中断的话,调用interrupt()方法:

t.interrupt(); //打断线程t的等待
ReentrantLock指定为公平锁:
ReentrantLock lock=new ReentrantLock(false); //参数为true表示为公平锁,false非公平.

Reentrantlock的Condition:

Condition也就是Java中的条件变量,条件变量的出现是为了更精细控制线程等待与唤醒,在Java5之前,线程的等待与唤醒依靠的是Object对象的wait()和notify()/notifyAll()方法,这样的处理不够精细。
对于一个固定容量的阻塞容器,可以使用上面所述的wait&notifyAll通过生产者/消费者模式来实现,但是这种做法有个局限性,就是在用notifyAll进行线程唤醒的时候,无法指定具体的某一个线程被唤醒,具有随机性。一种更好的方法是使用Condition的方式可以更加精确的指定哪些线程被唤醒。
使用condition与wait&notifyAll大体类似,但是更加细粒度。Wait&notifyAll的替换方法分别为await&signalAll.下面是实现一个泛型的同步有界容器的具体代码:

public class MyContainer2<T> {

        final private LinkedList<T> lists = new LinkedList<T>();
        final private int MAX = 10; //最多10个元素
        private int count = 0;
        private Lock lock = new ReentrantLock();
        private Condition producer = lock.newCondition();
        private Condition consumer = lock.newCondition(); //获取条件

        public void put(T t) {
                try {
                        lock.lock();
                        while(lists.size() == MAX) { //要用while不要用if
                                producer.await();
                        }
                        lists.add(t);
                        ++count;
                        consumer.signalAll(); //通知消费者线程进行消费
                } catch (InterruptedException e) {
                        e.printStackTrace();
                } finally {
                        lock.unlock();
                }
        }
        public T get() {
                T t = null;
                try {
                        lock.lock();
                        while(lists.size() == 0) {
                                consumer.await();
                        }
                        t = lists.removeFirst();
                        count --;
                        producer.signalAll(); //通知生产者进行生产
                } catch (InterruptedException e) {
                        e.printStackTrace();
                } finally {
                        lock.unlock();
                }
                return t;
        }

四种获取锁方法的区别

  1. lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行阻塞等待。
    由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

  2. tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
    tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

  3. lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
    注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。
    因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。

  4. 而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

猜你喜欢

转载自blog.csdn.net/wang7807564/article/details/80048351