JUC(java.util.concurrency)包中常用锁相关类

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 JUC(java.util.concurrency)包中常用锁相关类,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

java.util.concurrency.locks

java.util.concurrency.locksjava.util.concurrency(JUC)包下,它是一个提供了锁机制相关的类包,比如:Lock, Condition, ReadWriteLock等。

Lock接口

在JDK5之前,Java应用程序对于多线程的并发安全处理只能基于synchronized关键字解决,但是synchronized在有些场景中会存在一些短板。比如:它并不适合于所有的并发场景。Lock的出现可以解决synchronized在某些场景中的短板,它比synchronized更加灵活。

synchronized、wait

synchronized可以加锁,wait、notify可以看做加锁和解锁。但是synchronized方式加锁存在一定问题,因此出现了显式锁Lock。

synchronized的问题:

1、同步块的阻塞无法中断,不能Interruptibly

2、同步块的阻塞无法控制超时,无法自动解锁

3、同步块无法异步处理锁,即不能立即知道是否可以拿到锁

4、同步块无法根据条件灵活的加锁解锁,即只能跟同步块范围一致

Lock

Lock锁采用接口设计,使用方式灵活可控,性能开销小,属于java.util.concurrent.locks锁工具包
在这里插入图片描述

接口方法

在这里插入图片描述

方法 描述
void lock() 获取锁,类似synchronized (lock)
void lockInterruptibly() 获取锁,允许打断
Condition newCondition() 新增一个绑定到当前Lock的条件
final Lock lock = new ReentrantLock()
final Condition notEmpty = lock.newCondition()
boolean tryLock() 尝试无等待获取锁,成功则返回true
boolean tryLock(long time, TimeUnit unit) 尝试获取锁,成功则返回true,超时则退出
void unlock() 解锁,要求当前线程已获得锁,类似同步块结束

使用Lock

public class LockCounter {
    private int sum = 0;
    // 可重入锁+公平锁
    private Lock lock = new ReentrantLock(true);

    public int incrAndGet() {
        try {
            lock.lock();
            return ++sum;
        } finally {
            lock.unlock();
        }
    }

    public int getSum() {
        return sum;
    }

    public static void main(String[] args) {
        int loopNum = 10_0000;
        LockCounter counter = new LockCounter();
        // 从0到loopNum产生递增值序列,转成一个流,然后循环遍历0到loopNum数,进行求和
        IntStream.range(0, loopNum).parallel()
                .forEach(i -> counter.incrAndGet());
        System.out.println("counter.getSum() = " + counter.getSum());
    }
}
counter.getSum() = 100000

ReentrantLock重入锁

ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。

ReentrantLock具有与synchronized相同功能,但是会比synchronized更加灵活,具有更多的方法。

ReentrantLock底层基于AbstractQueuedSynchronizer实现。

ReentrantLock与Synchronized的异同

ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。

实现上两者不同:

synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但不够灵活。一般并发场景使用synchronized

ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中使用。

都是可重入:

synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁

ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

ReentrantLock实现公平、非公平锁

ReentrantLock可以实现公平、非公平锁。

公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。非公平锁则随机分配这种使用权。

ReentrantLock和synchronized一样,默认ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。

创建公平锁

ReentrantLock lock = new ReentrantLock(true);

创建非公平锁

ReentrantLock lock = new ReentrantLock(false);

使用示例

public class ReentrantLockDemo {

    private static int count = 0;

    // 公平重入锁
    static Lock lock = new ReentrantLock(true);

    public static void inc() {
        // 线程获得锁(互斥锁)
        lock.lock();
        try {
            Thread.sleep(1);
            count++;
            getLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放锁 
            lock.unlock();
        }
    }

    public static void getLock() {
        // 同一个线程再次来抢占锁 : 不需要再次抢占锁,而是只增加重入的次数
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> AtomicDemo.inc());
        t1.start();

        System.out.println("result:" + count);
    }
}
result:0

ReadWriteLock重入读写锁

ReadWriteLock管理一组锁:一个读锁,一个写锁。它适用于读多写少的并发情况。

读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。因此,在读写锁的实现时必须确保写操作对读操作的影响。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。

在这里插入图片描述

接口方法

Lock readLock(); 获取读锁,共享锁

Lock writeLock(); 获取写锁,独占锁,排斥读锁

使用ReadWriteLock

public class ReadWriteLockCounter {
    private int sum = 0;
    /**
     * 可重入、读写锁、公平/非公平锁
     * fair:true:公平锁 false:非公平锁
     * 公平锁意味排队靠前的优先
     * 非公平锁则都是同样机会
     */
    private ReadWriteLock lock = new ReentrantReadWriteLock(true);

    public int incrAndGet() {
        try {
            // 写锁 独占锁 被读锁排斥
            lock.writeLock().lock();
            return ++sum;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getSum() {
        try {
            // 读锁 共享锁 保证可见性
            lock.readLock().lock();
            return sum;
        } finally {
            lock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        int loopNum = 10_0000;
        ReadWriteLockCounter readWriteLockCounter = new ReadWriteLockCounter();
        // 从0到loopNum产生递增值序列,转成一个流,然后循环遍历0到loopNum数,进行求和
        IntStream.range(0, loopNum).parallel()
                .forEach(i -> readWriteLockCounter.incrAndGet());
        System.out.println("counter.getSum() = " + readWriteLockCounter.getSum());
        System.out.println("counter.getSum() = " + readWriteLockCounter.getSum());
    }
}
counter.getSum() = 100000
counter.getSum() = 100000

Condition信号量

Condition是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件( condition ),只有满足条件时,线程才会被唤醒

Condition具有线程的wait和notify的功能,有2个核心方法:

await:把当前线程阻塞挂起

signal:唤醒阻塞的线程

当调用await方法后,当前线程会释放锁并等待,而其他线程调用condition对象的signal或者signalall方法通知并被阻塞的线程,然后自己执行unlock释放锁,被唤醒的线程获得之前的锁继续执行,最后释放锁。

接口方法

在这里插入图片描述

方法 描述
void await() 等待信号,相当于Object的wait()
boolean await(long time, TimeUnit unit) 等待信号,超时则返回false
long awaitNanos(long nanosTimeout) 等待信号,直到被信号发送或中断, 或指定等待时间过去
void awaitUninterruptibly() 等待信号
boolean awaitUntil(Date deadline) 等待信号, 超时则返回false
void signal() 给一个等待线程发送唤醒信号, 相当于Object的notify ()
void signalAll() 给所有等待线程发送唤醒信号,相当于Object的notifyAll()

使用Condition

    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();
        // 重入锁
        Lock lock = new ReentrantLock();
        // 通过Lock.newCondition()创建,可以看做是Lock对象上的信号,类似于wait/notify
        Condition condition = lock.newCondition();
        int maxSize = 5;

        Producer producer = new Producer(queue, maxSize, lock, condition);
        Consumer consumer = new Consumer(queue, maxSize, lock, condition);

        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();
    }
}
public class Producer implements Runnable {

    private Queue<String> msg;

    private int maxSize;

    Lock lock;
    Condition condition;

    public Producer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;
            lock.lock();
            while (msg.size() == maxSize) {
                System.out.println("生产者队列满了,先等待");
                try {
                    condition.await(); // 阻塞线程并释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产消息:" + i);
            msg.add("生产者的消息内容" + i);
            condition.signal(); // 唤醒阻塞状态下的线程
            lock.unlock();
        }
    }
}
public class Consumer implements Runnable {
    private Queue<String> msg;

    private int maxSize;

    Lock lock;
    Condition condition;

    public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;
            lock.lock(); //synchronized
            while (msg.isEmpty()) {
                System.out.println("消费者队列空了,先等待");
                try {
                    condition.await(); //阻塞线程并释放锁   wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费消息:" + msg.remove());
            condition.signal(); //唤醒阻塞状态下的线程
            lock.unlock();
        }
    }
}

生产消息:1
生产消息:2
生产消息:3
生产消息:4
生产消息:5
生产者队列满了,先等待
消费消息:生产者的消息内容1
消费消息:生产者的消息内容2
消费消息:生产者的消息内容3
消费消息:生产者的消息内容4
消费消息:生产者的消息内容5
消费者队列空了,先等待
生产消息:6
生产消息:7
生产消息:8
生产消息:9

LockSupport锁当前线程

LockSupport类似于Thread 类的静态方法,专门处理执行代码的本线程。

接口方法

在这里插入图片描述

方法 描述
void park(Object blocker) 暂停当前线程
void parkNanos(Object blocker,long nanos) 暂停当前线程,不过有超时时间的限制
void parkUntil(Object blocker,long deadline) 暂停当前线程,直到某个时间
void park() 无期限暂停当前线程
void parkNanos(long nanos) 暂停当前线程,不过有超时时间的限制
void parkUntil(long deadline) 暂停当前线程,直到某个时间
void unpark(Thread thread) 恢复当前线程。传入Thread参数,是因为一个park线程,无法自己唤醒自己,所以需要其他线程来唤醒
Object getBlocker(Thread t)

使用LockSupport


    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 被唤醒");
        }, "t1");
        t1.start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + " 开始唤醒");
            LockSupport.unpark(t1);
        }, "t2").start();
    }
t1 开始执行
t2 开始执行
t2 开始唤醒
t1 被唤醒

用锁原则

1. 只在更新对象的成员变量时加锁

2. 只在访问可变的成员变量时加锁

3. 不在调用其他对象的方法时加锁

4. 降低锁范围:锁定代码的范围、作用域

5. 细分锁粒度:一个大锁,拆分成多个小锁

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136855.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!