Java高并发编程之ReentrantLock

ReentrantLock和synchronized一样,都是可重入的,但是它与synchronized是有区别的,在了解两者的区别之前,先尝试着,使用ReentrantLock替换synchronized。
先看下面synchronized的代码:

public class T016 {

    synchronized void m1() {
        for(int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
    }

    synchronized void m2() {
        System.out.println("m2...");
    }

    public static void main(String[] args) {
        T016 t016 = new T016();
        new Thread(t016::m1, "t1").start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(t016::m2, "t2").start();
    }
}

m1和m2都是同步方法,因此t2需要等待t1执行完毕获得t016对象的锁之后才能开始执行。
上面这段代码,如果用ReentrantLock代替synchronized,如下:

public class T017 {
    Lock lock = new ReentrantLock();
    
    void m1() {
        try {
            lock.lock();
            for(int i = 0; i < 10; i++) {
                Thread.sleep(1000);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    
    void m2() {
        lock.lock();
        System.out.println("m2...");
        lock.unlock();
    }

    public static void main(String[] args) {
        T017 t017 = new T017();
        new Thread(t017::m1, "t1").start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(t017::m2, "t2").start();
    }
}

上面代码中,当需要执行某段同步代码的时候,需要先锁定,即调用ReentrantLock对象的lock方法手动上锁,lock.lock()相当于synchronized(this)。在同步代码执行结束时,必须要必须要必须要手动释放锁,即调用ReentrantLock对象的unlock方法。
使用synchronized如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放。
到目前为止讲的是使用ReentrantLock替代synchronized,关于这两者的区别,有如下几点:
1、 使用用ReentrantLock可以进行“尝试锁定” tryLock,这样在无法锁定或者在指定时间内无法锁定,线程可以决定是否继续等待。
看下面代码:

public class T018 {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock();
            for(int i = 0; i < 10; i++) {
                Thread.sleep(1000);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    void m2() {
        boolean isLock = false;
        try {
            isLock = lock.tryLock(5, TimeUnit.SECONDS);
            System.out.println("m2...");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(isLock) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        T018 t018 = new T018();
        new Thread(t018::m1, "t1").start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(t018::m2, "t2").start();
    }
}

m2方法中lock.tryLock(5, TimeUnit.SECONDS)表示进行尝试锁定,如果无法立即锁定,则等待,等待时间为5秒,tryLock的时候,如果不传参数,则不需要等待,tryLock执行结束,会返回一个布尔值,表示是否已锁定,用一个boolean变量isLock保存起来,在释放锁的时候,先判断isLock是否为true,是则释放锁,如果不进行这个判断,在tryLock没有成功锁定的情况下,调用unlock会抛出IllegalMonitorStateException异常,这个细节要注意下。

2、使用Reentrantlock可以调用lockInterruptibly方法,对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断。
看下面代码:

public class T019 {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();

        Thread t1 = new Thread(()->{
            try {
                lock.lock();
                System.out.println("t1 start...");
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                System.out.println("t1 end.");
            } catch (InterruptedException e) {
                System.out.println("t1 interrupted!!");
            } finally {
                lock.unlock();
            }
        });
        t1.start();

        Thread t2 = new Thread(()->{
            boolean isLock = false;
            try {
                lock.lockInterruptibly();
                isLock = true;
                System.out.println("t2 start...");
                TimeUnit.SECONDS.sleep(5);
                System.out.println("t2 end.");
            } catch (InterruptedException e) {
                System.out.println("t2 interrupted!!");
            } finally {
                if(isLock) {
                    lock.unlock();
                }
            }
        });
        t2.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //打断线程2的等待
        t2.interrupt();
    }
}

t1线程睡眠Integer.MAX_VALUE秒,基本睡死了,因此t2线程很难等到锁,这种情况下,我们可能不希望t2线程继续空等,需要在某个时刻打断t2线程的等待,使用t2.interrupt()方法就可以办到,但要注意,在t2线程中,调用的锁定方法必须是lock.lockInterruptibly()而不是常规的lock.lock()。运行代码,打印结果如下:

t1 start...
t2 interrupted!!

3、Reentrantlock可以指定为公平锁
synchronized是非公平锁,而Reentrantlock是公平锁。

public class T020 extends Thread {
    public static ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
       for(int i = 0; i < 5; i++) {
           lock.lock();
           try {
               System.out.println(Thread.currentThread().getName() + "获得锁");
           } finally {
               lock.unlock();
           }
       }
    }

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

创建Reentrantlock对象的时候,ReentrantLock lock = new ReentrantLock(true)传入true表示要创建的是公平锁,如果确实是公平的,那么,上面代码执行起来,t1和t2应该交替得到锁,而不是一方占用另一方等待,运行结果如下:

Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁

这个结果证明了我们创建的锁是公平锁。

猜你喜欢

转载自blog.csdn.net/weixin_42486373/article/details/84031188