并发编程之JUC并发工具类的应用场景详解

常用并发同步工具类的真实应用场景

  首先我们来思考一个问题:并发编程是为了解决什么问题的?可以帮我们提升系统性能并且解决线程安全问题。

  jdk提供了比synchronized更加高级的各种同步工具,包括ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等,可以实现更加丰富的多线程操作。

Java并发知识体系| ProcessOn免费在线作图,在线流程图,在线思维导图

1. ReentrantLock

  ReentrantLock是一种可重入的独占锁,它允许同一个线程多次获取同一个锁而不会被阻塞。它的功能类似于synchronized是一种互斥锁,可以保证线程安全。相对于 synchronized, ReentrantLock具备如下特点:

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • 与 synchronized 一样,都支持可重入

它的主要应用场景是在多线程环境下对共享资源进行独占式访问,以保证数据的一致性和安全性

1.1. 常用API

Lock接口

  ReentrantLock实现了Lock接口规范,Lock接口API如下:

void lock()

获取锁,调用该方法当前线程会获取锁,当锁获得后,该方法返回

void lockInterruptibly() throws InterruptedException

可中断的获取锁,和lock()方法不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程

boolean tryLock()

尝试非阻塞的获取锁,调用该方法后立即返回。如果能够获取到返回true,否则返回false

boolean tryLock(long time, TimeUnit unit) throws InterruptedException

超时获取锁,当前线程在以下三种情况下会被返回:

当前线程在超时时间内获取了锁

当前线程在超时时间内被中断

超时时间结束,返回false

void unlock()

释放锁

Condition newCondition()

获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后,当前线程将释放锁

基本语法

//加锁  阻塞 
lock.lock(); 
try {  
    ...
} finally { 
    // 解锁 
    lock.unlock();  
}

//尝试加锁   非阻塞
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        ...
    } finally {
        lock.unlock();
    }
}

在使用时要注意 4 个问题:

  1. 默认情况下 ReentrantLock 为非公平锁而非公平锁;
  2. 加锁操作一定要放在 try 代码之前,这样可以避免未加锁成功又释放锁的异常,释放锁一定要放在 finally 中,否则可能会导致线程阻塞;
  3. ReentrantLock 和 synchronized 不一样,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样
  4. 加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常(可能会造成某一线程由于没有得到解锁指示而一直在等待解锁);

1.2. ReentrantLock使用

1.2.1. 独占锁:模拟抢票场景

 8张票,10个人抢,如果不加锁,会出现什么问题?

/**
 * 模拟抢票场景
 */
public class ReentrantLockDemo {

    /**
     * 默认是非公平锁
     */
    private final static ReentrantLock lock = new ReentrantLock();
    /**
     * 总票数
     */
    private static int tickets = 8;

    public void buyTicket(){
        lock.lock(); //获取锁  如果不加锁,会出现票超卖的情况
        try {
            if(tickets > 0){ //还有票  读操作
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "购买了第" + tickets-- + "张票"); //写操作
                //  buyTicket();  //用于演示可重入锁
            }else{
                System.out.println("票已经卖完了," + Thread.currentThread().getName() + "抢票失败");
            }
        }finally{
            lock.unlock();  //释放锁
        }
    }

    public static void main(String[] args) {
        ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
        //开启10个线程抢票
        for (int i = 1; i <= 10; i++){
            new Thread(() -> {
                //抢票
                reentrantLockDemo.buyTicket();
            },"线程"+i).start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("剩余票数:" + tickets);
    }

}

不加锁效果:出现超卖问题

加锁效果:结果正常,两个人抢票失败

1.2.2. 公平锁和非公平锁

ReentrantLock支持公平锁和非公平锁两种模式:

  • 公平锁:线程在获取锁时,按照等待的先后顺序获取锁。
  • 非公平锁:线程在获取锁时,不按照等待的先后顺序获取锁,而是随机获取锁。ReentrantLock默认是非公平锁

ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁

ReentrantLock lock = new ReentrantLock(true); //公平锁

比如买票的时候就有可能出现插队的场景,允许插队就是非公平锁,如下图:

1.2.3. 可重入锁

概述

  可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLocksynchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。在实际开发中,可重入锁常常应用于递归操作、调用同一个类中的其他方法、锁嵌套等场景中。

解释

可:可以
重:再次
入:进入
锁:同步锁

进入什么:进入同步域(即同步代码块/方法或显式锁锁定的代码)

一句话:可重入锁就是支持可重入,在没有释放锁时,还可以再次进入,因为持有锁的对象没有变,一直是一个对象持有这把锁。

可重入锁如下

1. synchronized示例

/**
 * @Description:使用synchronized
 * @Auther: Carl
 */
public class WhatReentrant1 {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println("第1次获取锁,这个锁是:" + this);
                    int index = 1;
                    while (true) {
                        synchronized (this) {
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
                        }
                        if (index == 10) {
                            break;
                        }
                    }
                }
            }
        }).start();
    }
}

2. ReentrantLock示例

/**
 * @Description: 使用ReentrantLock
 * @Auther: Carl
 */
public class WhatReentrant2 {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("第1次获取锁,这个锁是:" + lock);
                    int index = 1;
                    while (true) {
                        try {
                            lock.lock();
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);
                            try {
                                Thread.sleep(new Random().nextInt(200));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            if (index == 10) {
                                break;
                            }
                        } finally {
                            lock.unlock();
                        }
                    }
                } finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值