ReentrantLock类

ReentrantLock

ReentrantLock是一个互斥锁,也是一个可重入锁(Reentrant就是再次进入的意思)。ReentrantLock锁在同一个时间点只能被一个线程锁持有,但是它可以被单个线程多次获取,每获取一次AQSstate就加1,每释放一次state就减1。还记得synchronized嘛,它也是可重入的,一个同步方法调用另外一个同步方法是没有问题的。

在使用上无非就是获取锁和释放锁,我们完全可以用它来实现synchronized的功能

我要实现一个程序,由两条线程去输出100到0,下面是有问题的程序代码


public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while(counter.getCount()>=0)
                    counter.desc();
            }
        };

        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

class Counter{
    private int count = 100;    
    public void desc(){
        System.out.println(Thread.currentThread().getName() +"--->"+count);
        count--;
    }

    public int getCount() {
        return count;
    }
}   


 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

某次执行输出结果,很明显并没有达到我的要求。

.....
Thread-1--->6
Thread-1--->5
Thread-1--->4
Thread-1--->3
Thread-1--->2
Thread-1--->1
Thread-1--->0
Thread-0--->15

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

于是我用ReentrantLock改写一下Counter类的desc方法,你要注意了,千万不要傻傻地在desc方法内部创建一个ReentrantLock对象,这样每次线程调用的时候用的都是一个新锁,还谈什么互斥呀,就像同步方法和静态同步方法,它们的锁都不是同一个,是互斥不了的。现在运行代码是没错的了

class Counter {
    private int count = 100;
    private Lock lock = new ReentrantLock();

    public void desc() {
        lock.lock();//上锁

        if (count >= 0){
            System.out.println(Thread.currentThread().getName() + "--->" + count);
            count--;
        }

        lock.unlock();//释放锁
    }

    public int getCount() {
        return count;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

上面代码还是有问题的,那就是锁的释放,如果在上锁了,后面的代码抛出异常没能释放锁,你说完不完蛋!!?所以锁的释放一定要在try-finally块finally中,就像是JDBC中释放数据库连接那样。这一点还是synchronized比较方便,不用我们自己释放锁。

Condition

到了这里就要谈到Condition了,它需要与 Lock 联合使用,它的作用就是代替Object的那些监视器方法,Condition 中的await()signal()signalAll()方法分别对应着Objectwait()notify()notifyAll()方法。

不过一个它比较牛逼的一点是,一个Lock可以关联多个Condition,这样子玩起来就很灵活了,想要各个方法按什么顺序执行都行。还是上面那个例子,我想让两个线程和谐点,你输出一个数,然后我又输出下一个数,这样子交替执行,实现代码如下

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

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (counter.getCount() >= 0) {
                    counter.desc1();
                }
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (counter.getCount() >= 0) {
                    counter.desc2();
                }
            }
        }).start();
    }
}

class Counter {
    private int count = 100;
    private Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    boolean state = true;

    public void desc1() {
        lock.lock();// 上锁

        try {
            while (state)
                condition1.await();// 线程等待

            if (count >= 0) {
                System.out.println(Thread.currentThread().getName() + "--->" + count);
                count--;
            }
            state = true;// 改变状态
            condition2.signal();// 唤醒调用了condition2.await()线程

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();// 释放锁
        }
    }

    public void desc2() {
        lock.lock();// 上锁

        try {
            while (!state)
                condition2.await();// 线程等待

            if (count >= 0) {
                System.out.println(Thread.currentThread().getName() + "--->" + count);
                count--;
            }
            state = false;// 改变状态
            condition1.signal();// 唤醒调用了condition1.await()线程

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();// 释放锁
        }
    }

    public int getCount() {
        return count;
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

输出结果

.....
Thread-1--->10
Thread-0--->9
Thread-1--->8
Thread-0--->7
Thread-1--->6
Thread-0--->5
Thread-1--->4
Thread-0--->3
Thread-1--->2
Thread-0--->1
Thread-1--->0

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面代码不难,所以不再这多解释了,不过按照上面的例子你就可以举一反三了,我记得有道线程的题目是让三个线程不断交替输出什么鬼的,t1->t2->t3->t1->t2->t3….,根据上面的例子我相信你能解决这个问题的,可以拿这个题目练一下手,熟悉一下。

最后要提一下的是ReentrantLock有两个构造方法,默认的构造方法会让它成为一个非公平锁,而如果你想创建一个公平锁则用ReentrantLock(boolean fair)传入一个true创建ReentrantLock实例

转载自:

https://blog.csdn.net/TimHeath/article/details/71643008

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我知道了。首先我需要解释一下 synchronized 关键字、volatile、ReentrantLock 与阻塞队列 LinkedBlockingQueue<E> 的概念。 synchronized 关键字是 Java 中用于实现同步的关键字,可以用来修饰代码块、方法和静态方法,用于实现线程间的同步。 volatile 是一个关键字,用于修饰变量,用于实现多线程之间的可见性,即当一个线程修改了一个 volatile 变量的值时,其他线程可以立即看到这个修改。 ReentrantLock 是一个可重入的互斥锁,可以用来实现线程间的同步,与 synchronized 关键字相比,ReentrantLock 提供了更多的功能,如公平锁和可中断锁等。 阻塞队列 LinkedBlockingQueue<E> 是一个基于链表实现的阻塞队列,在多线程编程中可以用来实现线程间的同步。 接下来,我将使用这些同步方法实现主线程与子线程的同步,并写出实验小结。 代码如下: ``` import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.locks.ReentrantLock; public class Main { private volatile boolean flag = false; private final ReentrantLock lock = new ReentrantLock(); private final LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(); public static void main(String[] args) { Main main = new Main(); main.start(); } private void start() { Thread t = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; lock.lock(); try { queue.put(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }); t.start(); lock.lock(); try { while (!flag) { Thread.sleep(100); } System.out.println(queue.take()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } ``` 在这个例子中,主线程和子线程都需要修改和访问共享变量 flag,因此需要使用 volatile 关键字来实现可见性。 主线程需要等待子线程修改 flag 后才能继续执行,因此可以使用 while 循环不断检查 flag 是否被修改,这里使用了 Thread.sleep(100) 来减少 CPU 的占用率。 子线程需要将一个元素加入到队列中,并通知主线程可以继续执行,因此可以使用 ReentrantLock 和 LinkedBlockingQueue 来实现线程间的同步。 实验小结: 在多线程编程中,同步是一个非常重要的概念,可以用来避免线程间的竞争和冲突。在 Java 中,可以使用 synchronized 关键字、volatile、ReentrantLock 和阻塞队列 LinkedBlockingQueue<E> 等方法来实现线程间的同步。在实际编程中,应根据情况选择不同的同步方法,以保证程序的正确性和效率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值