Java多线程(七):ReentrantLock

本文深入讲解ReentrantLock的使用方法,包括加锁解锁机制、条件变量的运用、公平锁与非公平锁的区别及其实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

加锁和解锁

我们来看下ReentrantLock的基本用法
ThreadDomain35类

public class ThreadDomain35 {

    private Lock lock = new ReentrantLock();

    public void testMethod()
    {
        try
        {
            lock.lock();
            for (int i = 0; i < 2; i++)
            {
                System.out.println("ThreadName = " + Thread.currentThread().getName() + ", i  = " + i);
            }
        }
        finally
        {
            lock.unlock();
        }
    }
}

线程和main方法

public class MyThread35 extends Thread {

    private ThreadDomain35 td;

    public MyThread35(ThreadDomain35 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.testMethod();
    }

    public static void main(String[] args)
    {
        ThreadDomain35 td = new ThreadDomain35();
        MyThread35 mt0 = new MyThread35(td);
        MyThread35 mt1 = new MyThread35(td);
        MyThread35 mt2 = new MyThread35(td);
        mt0.start();
        mt1.start();
        mt2.start();
    }
}

输出结果

ThreadName = Thread-2, i  = 0
ThreadName = Thread-2, i  = 1
ThreadName = Thread-0, i  = 0
ThreadName = Thread-0, i  = 1
ThreadName = Thread-1, i  = 0
ThreadName = Thread-1, i  = 1

一个线程必须执行完才能执行下一个线程,说明ReentrantLock可以加锁。

ReentrantLock持有的对象监视器和synchronized不同

ThreadDomain37类,methodB用synchronized修饰

public class ThreadDomain37 {
    private Lock lock = new ReentrantLock();

    public void methodA()
    {
        try
        {
            lock.lock();
            System.out.println("MethodA begin ThreadName = " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("MethodA end ThreadName = " + Thread.currentThread().getName());
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }

    }

    public synchronized void methodB()
    {
        System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
        System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
    }
}

MyThread37_0类

public class MyThread37_0 extends Thread {

    private ThreadDomain37 td;

    public MyThread37_0(ThreadDomain37 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.methodA();
    }
}

MyThread37_1类

public class MyThread37_1 extends Thread {
    private ThreadDomain37 td;

    public MyThread37_1(ThreadDomain37 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.methodB();
    }
}

MyThread37_main方法

public class MyThread37_main {

    public static void main(String[] args)
    {
        ThreadDomain37 td = new ThreadDomain37();
        MyThread37_0 mt0 = new MyThread37_0(td);
        MyThread37_1 mt1 = new MyThread37_1(td);
        mt0.start();
        mt1.start();
    }

}

运行结果如下

MethodA begin ThreadName = Thread-0
MethodB begin ThreadName = Thread-1
MethodB begin ThreadName = Thread-1
MethodA end ThreadName = Thread-0

加了synchronized依然是异步执行,说明ReentrantLock和synchronized持有的对象监视器不同。ReentrantLock需要手动加锁和释放锁。

Condition

synchronized与wait()和nitofy()/notifyAll()方法可以实现等待/唤醒模型,ReentrantLock同样可以,需要借助Condition的await()和signal/signalAll(),await()释放锁。
ThreadDomain38类

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

    public void await()
    {
        try
        {
            lock.lock();
            System.out.println("await时间为:" + System.currentTimeMillis());
            condition.await();
            System.out.println("await等待结束");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }

    public void signal()
    {
        try
        {
            lock.lock();
            System.out.println("signal时间为:" + System.currentTimeMillis());
            condition.signal();
            System.out.println("signal等待结束");
        }
        finally
        {
            lock.unlock();
        }
    }
}

MyThread38类,线程和main方法

public class MyThread38 extends Thread
{
    private ThreadDomain38 td;

    public MyThread38(ThreadDomain38 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.await();
    }

    public static void main(String[] args) throws Exception
    {
        ThreadDomain38 td = new ThreadDomain38();
        MyThread38 mt = new MyThread38(td);
        mt.start();
        Thread.sleep(3000);
        td.signal();
    }
}

运行结果如下

await时间为:1563505465346
signal时间为:1563505468345
signal等待结束
await等待结束

可以看到,ReentrantLock和Condition实现了等待/通知模型。
一个Lock可以创建多个Condition;
notify()唤醒的线程是随机的,signal()可以有选择性地唤醒。

ReentrantLock中的方法

公平锁和非公平锁
ReentrantLock可以指定公平锁和非公平锁,公平锁根据线程运行的顺序获取锁,非公平锁则通过抢占获得锁,不按线程运行顺序。synchronized是非公平锁。在ReentrantLock(boolean fair)构造函数传入true/false来指定公平锁/非公平锁。
看个例子
ThreadDomain39类和main方法

public class ThreadDomain39 {
    private Lock lock = new ReentrantLock(true);

    public void testMethod()
    {
        try
        {
            lock.lock();
            System.out.println("ThreadName" + Thread.currentThread().getName() + "获得锁");
        }
        finally
        {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception
    {
        final ThreadDomain39 td = new ThreadDomain39();
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                System.out.println("线程" + Thread.currentThread().getName() + "运行了");
                td.testMethod();
            }
        };
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++)
            threads[i] = new Thread(runnable);
        for (int i = 0; i < 5; i++)
            threads[i].start();
    }
}

输出结果如下

线程Thread-0运行了
ThreadNameThread-0获得锁
线程Thread-1运行了
线程Thread-2运行了
ThreadNameThread-1获得锁
线程Thread-3运行了
线程Thread-4运行了
ThreadNameThread-2获得锁
ThreadNameThread-3获得锁
ThreadNameThread-4获得锁

可以看到公平锁获得锁的顺序和线程运行的顺序相同。公平锁尽可能地让线程获取锁的顺序和线程运行顺序保持一致,再执行几次,可能不一致。
ReentrantLock构造函数传入false,输出结果如下:

线程Thread-0运行了
线程Thread-2运行了
线程Thread-4运行了
线程Thread-3运行了
ThreadNameThread-0获得锁
线程Thread-1运行了
ThreadNameThread-1获得锁
ThreadNameThread-2获得锁
ThreadNameThread-4获得锁
ThreadNameThread-3获得锁

非公平锁获得锁的顺序和线程运行的顺序不同

getHoldCount()

获取当前线程调用lock()的次数,一般debug使用。
看个例子

public class ThreadDomain40 {
    private ReentrantLock lock = new ReentrantLock();

    public void testMethod1()
    {
        try
        {
            lock.lock();
            System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount());
            testMethod2();
        }
        finally
        {
            lock.unlock();
        }
    }

    public void testMethod2()
    {
        try
        {
            lock.lock();
            System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount());
        }
        finally
        {
            lock.unlock();
        }
    }



    public static void main(String[] args)
    {
        ThreadDomain40 td = new ThreadDomain40();
        td.testMethod1();
    }


}

输出结果如下

testMethod1 getHoldCount = 1
testMethod2 getHoldCount = 2

可以看到,testMethod1()被调用了一次,testMethod2()被调用了两次,ReentrantLock和synchronized一样,锁都是可重入的。

getQueueLength()和isFair()

getQueueLength()获取等待的线程数量,isFair()判断是否是公平锁。
ThreadDomain41类和main方法,Thread.sleep(2000)使第一个线程之后的线程都来不及启动,Thread.sleep(Integer.MAX_VALUE)使线程无法unlock()。

public class ThreadDomain41 {
    public ReentrantLock lock = new ReentrantLock();

    public void testMethod()
    {
        try
        {
            lock.lock();
            System.out.println("ThreadName = " + Thread.currentThread().getName() + "进入方法!");
            System.out.println("是否公平锁?" + lock.isFair());
            Thread.sleep(Integer.MAX_VALUE);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        final ThreadDomain41 td = new ThreadDomain41();
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                td.testMethod();
            }
        };
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++)
            threads[i] = new Thread(runnable);
        for (int i = 0; i < 10; i++)
            threads[i].start();
        Thread.sleep(2000);
        System.out.println("有" + td.lock.getQueueLength() + "个线程正在等待!");
    }
}

输出结果如下

ThreadName = Thread-1进入方法!
是否公平锁?false
有9个线程正在等待!

ReentrantLock默认是非公平锁,只有一个线程lock(),9个线程在等待。

hasQueuedThread()和hasQueuedThreads()

hasQueuedThread(Thread thread)查询指定线程是否在等待锁,hasQueuedThreads()查询是否有线程在等待锁。
看个例子
ThreadDomain41类和main方法,和上面例子类似,Thread.sleep(Integer.MAX_VALUE); 让线程不释放锁,Thread.sleep(2000);让第一个线程之后的线程都无法启动。

public class ThreadDomain42 extends ReentrantLock {
    public void waitMethod()
    {
        try
        {
            lock();
            Thread.sleep(Integer.MAX_VALUE);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        final ThreadDomain42 td = new ThreadDomain42();
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                td.waitMethod();
            }
        };
        Thread t0 = new Thread(runnable);
        t0.start();
        Thread.sleep(500);
        Thread t1 = new Thread(runnable);
        t1.start();
        Thread.sleep(500);
        Thread t2 = new Thread(runnable);
        t2.start();
        Thread.sleep(500);
        System.out.println("t0 is waiting?" + td.hasQueuedThread(t0));
        System.out.println("t1 is waiting?" + td.hasQueuedThread(t1));
        System.out.println("t2 is waiting?" + td.hasQueuedThread(t2));
        System.out.println("Is any thread waiting?" + td.hasQueuedThreads());
    }
}

输出结果如下

t0 is waiting?false
t1 is waiting?true
t2 is waiting?true
Is any thread waiting?true

t0线程获得了锁,t0没有释放锁,导致t1,t2等待锁。

isHeldByCurrentThread()和isLocked()

isHeldByCurrentThread()判断锁是否由当前线程持有,isLocked()判断锁是否由任意线程持有。
请看示例
ThreadDomain43类和main方法

public class ThreadDomain43 extends ReentrantLock {
    public void testMethod()
    {
        try
        {
            lock();
            System.out.println(Thread.currentThread().getName() + "线程持有了锁!");
            System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
                    isHeldByCurrentThread());
            System.out.println("是否任意线程持有了锁?" + isLocked());
        } finally
        {
            unlock();
        }
    }

    public void testHoldLock()
    {
        System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
                isHeldByCurrentThread());
        System.out.println("是否任意线程持有了锁?" + isLocked());
    }

    public static void main(String[] args)
    {
        final ThreadDomain43 td = new ThreadDomain43();
        Runnable runnable0 = new Runnable()
        {
            public void run()
            {
                td.testMethod();
            }
        };
        Runnable runnable1 = new Runnable()
        {
            public void run()
            {
                td.testHoldLock();
            }
        };
        Thread t0 = new Thread(runnable0);
        Thread t1 = new Thread(runnable1);
        t0.start();
        t1.start();
    }
}

输出结果如下

Thread-0线程持有了锁!
Thread-1线程是否持有锁?false
Thread-0线程是否持有锁?true
是否任意线程持有了锁?true
是否任意线程持有了锁?true

Thread-0线程testMethod方法持有锁,Thread-1线程testHoldLock方法没有lock操作,所以不持有锁。

tryLock()和tryLock(long timeout, TimeUnit unit)

tryLock()有加锁的功能,获得了锁且锁没有被另外一个线程持有,此时返回true,否则返回false,可以有效避免死锁。tryLock(long timeout, TimeUnit unit)表示在给定的时间内获得了锁,锁没有被其他线程持有,且不处于中断状态。返回true,否则返回false;
看个例子

public class MyThread39 {
    public static void main(String[] args) {

        System.out.println("开始");
        final Lock lock = new ReentrantLock();
        new Thread() {
            @Override
            public void run() {
                String tName = Thread.currentThread().getName();
                if (lock.tryLock()) {
                    System.out.println(tName + "获取到锁!");
                } else {
                    System.out.println(tName + "获取不到锁!");
                    return;
                }
                try {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(tName + ":" + i);
                    }
                    Thread.sleep(5000);
                } catch (Exception e) {
                    System.out.println(tName + "出错了!");
                } finally {
                    System.out.println(tName + "释放锁!");
                    lock.unlock();
                }

            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                String tName = Thread.currentThread().getName();

                try {
                    if (lock.tryLock(1,TimeUnit.SECONDS)) {
                        System.out.println(tName + "获取到锁!");
                    } else {
                        System.out.println(tName + "获取不到锁!");
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                try {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(tName + ":" + i);
                    }

                } catch (Exception e) {
                    System.out.println(tName + "出错");
                } finally {
                    System.out.println(tName + "释放锁!");
                    lock.unlock();
                }
            }
        }.start();

        System.out.println("结束");
    }
}

输出结果如下

开始
Thread-0获取到锁!
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
结束
Thread-1获取不到锁!
Thread-0释放锁!

Thread-0先获得了锁,且sleep了5秒,导致Thread-1获取不到锁,我们给Thread-1的tryLock设置1秒,一秒内获取不到锁就会返回false。
如果Thread.sleep(0),那么Thread-0和Thread-1都可以获得锁,园友可以自己试下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值