Java线程学习之ReentrantLock锁


了解过synchronized关键字,它的使用比较简单。相对于重入锁而言,控制资源也不太灵活,而ReentrantLock重入锁是由开发人员手动操作,何时加锁与何时释放锁,控制起来更加灵活,ReentrantLock之所以称之为重入锁,是因为同一线程可以反复进入持有锁。 锁的获取和释放需要成对使用,如果释放锁的次数大于获取锁的次数,将会报 java.lang.IllegalMonitorStateException异常,如果释放锁的次数小于获取锁的次数相当于线程还持有这个锁。

         ReentrantLock   主要的方法有:

    lock() :获取锁,如果锁已经被占用,则会等待

    unlock() 释放锁

    isHeldByCurrentThread() 当前线程是否持有锁

    tryLock() 尝试获得锁 ,如果成功,就会返回true ,否则会返回false

              tryLock(long timeout, TimeUnit unit) 在给定的时间内尝试获得锁 第一个参数是时间长度,第二个参数是时间单位

    lockInterruptibly() 获得锁,但优先相应中断

举个例子:这是利用重入锁,实现两个线程分别进行数字累加最后结果20万

package com.example.demo.bingfa;

import java.util.concurrent.locks.ReentrantLock;

public class ReenterLock implements Runnable {
   public static ReentrantLock lock=new ReentrantLock();
   public static int i =0;
    @Override
    public void run() {
        for (int j=0;j<100000;j++){
            //获取锁
           lock.lock();
            try {
                i++;
            }catch (Exception e){
                e.printStackTrace();
            } finally{
                //释放锁
                lock.unlock();
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLock reenterLock=new ReenterLock();
        Thread t1=new Thread(reenterLock);
        Thread t2=new Thread(reenterLock);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}

 响应中断

     如果一个线程在等待锁,那么就会有两个结果,要么是它获得这把锁继续执行,要么他就继续等待。而使用重入锁,就会还有一种可能,线程能被中断。打个比方:周末你和朋友约好,一起去打乒乓球,结果你在等他过程中,突然收到他电话,说有事来不了了,这时你就去打不了球了。那你肯定中断这个等待。

  中断响应提供了一套类似的机制,如果一个线程正在等待锁,那么它可以收到一个通知,被告知无法再等待,可以停止工作了。 可以理解为,在线程等待过程中,收到了中断请求时,就直接进行中断,不再进行线程等待。

比如:

package com.example.demo.bingfa;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 该例子就是进行响应锁中断,先让两个线程互相请对方资源形成死锁,再利用锁2进行中断,让其进行,进行中断请求
 */
public class InLock implements Runnable {
    int lock;
    public static ReentrantLock lock1=new ReentrantLock();
    public static ReentrantLock lock2=new ReentrantLock();
    public InLock(int lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        try {
            if (lock==1){
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e){}
                lock2.lockInterruptibly();
            }else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e){}
                lock1.lockInterruptibly();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            if(lock1.isHeldByCurrentThread())lock1.unlock();
            if (lock2.isHeldByCurrentThread())lock2.unlock();
            System.out.println(Thread.currentThread().getId()+":线程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InLock r1=new InLock(1);
        InLock r2=new InLock(2);
        Thread t1=new Thread(r1);
        Thread t2=new Thread(r2);
        t1.start();
        t2.start();
        Thread.sleep(1000);

          t2.interrupt();
    }
}

     

等待超时: 

????线程在等待获取锁的过程中设置一个超时时间,当等待时间大于超时时间时,会退出,避免一直等待下去,造成死锁。就像:周末你和朋友约好,一起去打乒乓球,结果你在等他两个小时,他还不到,你就回去了,避免了自己一直等待下去的情况

package com.example.demo.bingfa;

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

/**
 * 先创建两个子线程,第一个线程启动时会申请获得锁,获得后持有六秒。第二个线程运行时去尝试获得锁,但是因为此时锁被第
 * 一个线程所持有,它会等待5秒,仍然获取不到,就会失败
 * 
 */
public class TimeLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            //申请获得锁,等待5秒,如果获得锁,就返回true,否则返回false
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                //线程休眠6秒
                Thread.sleep(6000);
            } else {
                System.out.println("get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread())
                lock.unlock();
        }
    }

    public static void main(String[] args) {
        TimeLock timeLock = new TimeLock();
        Thread t1 = new Thread(timeLock);
        Thread t2 = new Thread(timeLock);
        t1.start();
        t2.start();
    }


}

公平锁

????很多时候,重入锁都是非公平的,这里的公平指的是如果多个线程申请获得锁的次序与这些线程获得锁的顺序一致的时候,此时锁就是公平锁,反之就是非公平锁,比如,去窗口买票,如果一堆人都围在窗口,那么卖票员就会从中选择一个进行售票。如果这堆人排序买票,那么售票员的售票顺序也是依照次序来的。在这里一堆人就像多个线程,售票员售票就像获得锁的过程。可能一堆人都围在窗口买票,看着不雅观,但是它不需要人来维持秩序,减少了维持秩序的人力成本,而排序购票就需要专门有个人来维持秩序,增加了成本。比照着线程,多个这个人就像公平锁中的有序队列。所以公平锁的实现成本比较高,性能也会相对低下。

package com.example.demo.bingfa;

import java.util.concurrent.locks.ReentrantLock;

public class FairLock implements Runnable {
    //将重入锁置为公平锁
    public  static ReentrantLock fairLock=new ReentrantLock(true);
    @Override
    public void run() {
        //一直为真,循环下去,从而确定当前那个线程获得了锁
        while (true){
            try {
                fairLock.lock();
                System.out.println(Thread.currentThread().getName()+"获得锁");

            }finally {
                fairLock.unlock();
            }
        }
    }

    /**
     * 创建两个线程,并启动
     * @param args
     */
    public static void main(String[] args) {
        FairLock f1=new FairLock();
        Thread t1=new Thread(f1,"Thread_r1");
        Thread t2=new Thread(f1,"Thread_r2");
        t1.start();
        t2.start();
    }
}