JUC包中的各种同步器

1. ReentrantLock

先来个简单案例:

	// 1.创建一个锁
      ReentrantLock reentrantLock = new ReentrantLock();
        Thread t1 = new Thread(() -> {

            try {
            	// 2.加锁
                reentrantLock.lock();
                for (int i = 0; i < 10;i++){
                    // 减缓打印速度
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("线程1:运行中");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
            //3.解锁
                reentrantLock.unlock();
            }
        },"T1");
        t1.start();

案例很简单,就是创建一个锁,然后加锁,解锁。

1.1. 常用API

  • 支持公平锁和非公平锁,公平锁就是按照顺序进入锁等待队列来获取锁,非公平锁是先尝试获取锁,获取失败后进入等待队列

    • 公平锁:ReentrantLock reentrantLock = new ReentrantLock(true);
    • 非公平锁:ReentrantLock reentrantLock = new ReentrantLock(false);
    • 默认是非公平锁,源码很简单,一看便知。
  • lock():加锁,锁被占有,则阻塞等待。

  • tryLock():尝试获取锁,不会阻塞线程,返回值是Boolean 类型,代表是否获取到锁。

  • tryLock(timeout,Times):尝试获取锁,在参数指定时间内获取不到锁则线程继续执行。

  • lockInterruptibly() :加锁,这个锁支持被打断,比如线程1 调用lockInterruptibly() 加锁成功,线程2 因等待锁被阻塞,那么线程2 支持通过interrupt() 打断。

  • hasQueuedThreads() 是否有线程在等待获取当前锁

  • isLocked():当前锁是否被占有。

1.2. Condition 的使用

Condition 是可以唤醒指定线程,用于生产者消费者场景的线程调度
先来个场景,有一个仓库,5个线程往里生产,5个线程消费,这10个线程共享一个仓库资源,所以得使用同一把锁,当仓库满了需要生产线程等待,仓库空的时会需要消费线程等待。

案例伪代码:


        ReentrantLock reentrantLock = new ReentrantLock();

        // 消费者
        Condition con1 = reentrantLock.newCondition();

        // 生产者
        Condition con2 = reentrantLock.newCondition();

        Thread t1 = new Thread(() -> {
            try {
                reentrantLock.lock();
                // 假如仓库已满,进行等待,并且唤醒消费者,
                if(假如仓库已满){
                    // 生产者组等待
                    con2.await();
                    // 消费者组唤醒
                    con1.signal();
                }else{
                    // 创建产品,添加到仓库1个产品
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                reentrantLock.unlock();
            }
        },"T1");
        t1.start();

        Thread t2 = new Thread(() -> {
            try {
                reentrantLock.lock();
                // 假如仓库已空,进行等待,并且唤醒生产者组线程,
                if(假如仓库已空){
                    // 消费者组等待
                    con1.await();
                    // 生产者组唤醒
                    con2.signal();
                }else{
                    // 消费产品,仓库产品数减少1个
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                reentrantLock.unlock();
            }
        },"T1");
        t2.start();

总结:被指定con1 执行wait 的方法,只能被con1 signal方法唤醒。消费者和生产者共享仓库资源,共同使用同一锁对象,但是可以被Condition 对象分成两个组,实现线程的调度。

2. CountDownLatch用法

2.1. 案例

这个比较简单,来个案例

    public static void countDownLatch() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10);
        System.out.println("==========运行开始==============");
        new Thread(() -> {
        // latch.getCount()  初始值 等于构造参数变量 10 
            while (latch.getCount() > 0){
                System.out.println("latch.getCount()"+latch.getCount());
                // 执行方法:latch.getCount() 减 1
                latch.countDown();
                sleep(1);
            }
        }).start();
		// 这里一直会阻塞直到 latch.getCount() = 0时,解除阻塞
        latch.await();
        System.out.println("==========运行结束==============");
    }

当主线程执行 latch.await() 方法时,会被阻塞,直到另一个线程一直执行 latch.countDown(); 把 CountDownLatch 内部对象的int 变量减为0,主线程才能继续执行。

案例中是多个线程执行 latch.countDown(); 单个线程await,其实也可以多个线程await,单线程countDown,大家要明白意思灵活多变。

注意 await() 方法不要和 wait() 方法混淆,await()CountDownLatch自带的,wait() 方法是继承自 Object

2.2 API

api更简单,只有如下3个方法

  • latch.await() 线程等待
  • await(long timeout, TimeUnit unit) 线程等待指定时长,时间到了,无论CountDownLatch内部int 变量是否减至0,线程都会继续执行
  • latch.getCount() 获取CountDownLatch内部int 变量值

3. CyclicBarrier

    public static void  cyclicBarrier(){
        Runnable runnable = () -> System.out.println("人满,发车");
        // 这里模拟
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3,runnable);
        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"-上车等待await...");
                try {
                	// 线程执行到这里都会阻塞,同时cyclicBarrier 计数器减1
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName()+"-await结束,准备出发...");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
    }
Thread-0-上车等待await...
Thread-2-上车等待await...
Thread-1-上车等待await...
人满,发车
Thread-1-await结束,准备出发...
Thread-2-await结束,准备出发...
Thread-0-await结束,准备出发...

CyclicBarrier 有两个参数 int 类型 parties 、runable 类型,线程执行cyclicBarrier.await(); 是会阻塞,同时在parties 减 1,当parties 减到0 时,执行runable ,然后所有被 cyclicBarrier.await();阻塞的线程继续执行

注意 await() 方法不要和 wait() 方法混淆,await()CyclicBarrier自带的,wait() 方法是继承自 Object

4. Phaser

4.1. 常用API

  • 构造方法

    • Phaser(): 参与任务数0
    • Phaser(int parties) :指定初始参与任务数
    • Phaser(Phaser parent):指定parent阶段器, 子对象作为一个整体加入parent对象, 当子对象中没有参与者时,会自动从parent对象解除注册
    • Phaser(Phaser parent,int parties) : 集合上面两个方法
  • 增减参与任务数方法

    • int register() 增加一个任务数,返回当前阶段号。
    • int bulkRegister(int parties) 增加指定任务个数,返回当前阶段号。
    • int arriveAndDeregister() 减少一个任务数,返回当前阶段号。
  • 到达、等待方法

    • int arrive() 到达(任务完成),返回当前阶段号。
    • int arriveAndAwaitAdvance() 到达后等待其他任务到达,返回到达阶段号。
    • int awaitAdvance(int phase) 在指定阶段等待(必须是当前阶段才有效)
    • int awaitAdvanceInterruptibly(int phase) 阶段到达触发动作
    • int awaitAdvanceInterruptiBly(int phase,long timeout,TimeUnit unit)
    • protected boolean onAdvance(int phase,int registeredParties)类似CyclicBarrier的触发命令,通过重写该方法来增加阶段到达动作,phase代表当前阶段,registeredParties代表当前阶段执行任务数,该方法返回true将终结Phaser对象,后面阶段不会再触发。

大白话解释:多个线程干活,活干完调用arrive() 报告,每干完 parties 数量的活,执行一次onAdvance()方法,阶段号就是每干够parties数量任务,阶段号加1

    public static void  phaser(){
        Phaser phaser = new Phaser(3){
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("任务到达第"+phase+"阶段,本阶段注册线程数:"+registeredParties);
                //返回true,终止触发执行后续阶段
                return false ;
            }
        };
        for (int i = 0; i < 15; i++) {
            new Thread(() -> {
                
                sleep(1);
                // arrive() 方法表示任务完成,这里几个线程干活自己定,干完调用arrive() 方法就行
                System.out.println("phaser.arrive()="+phaser.arrive());
            }).start();
        }

//        for (int i = 0; i < 3; i++) {
              // 等待当前阶段完成
//            phaser.awaitAdvance(phaser.getPhase());
//            System.out.println("执行阶段 = "+phaser.getPhase());
//            System.out.println("phaser.register() = "+phaser.register());
//        }
        System.out.println("======Main 运行结束=====");
    }

5. Semaphore

  • Semaphore semaphore = new Semaphore(2); 创建一个semaphore 有两个信号
  • semaphore.acquire() 获得一个信号,semaphore 有剩余信号,继续执行,没有则阻塞
  • semaphore.release() 释放信号 线程执行完释放
  • 限流场景,和令牌桶意思一样
    public static void semaphore(){

        Semaphore semaphore = new Semaphore(2);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                sleep(2);
                System.out.println("拿到信号,执行任务");
                semaphore.release();
            }).start();
        }
    }

6. Exchanger

  • 作用:交换线程的数据
  • exchanger.exchange(s) 方法是阻塞的
public static void main(String[] args) {
    Exchanger<String> exchanger = new Exchanger<>();
    new Thread(()->{
        String s = "T1";
        s = exchanger.exchange(s);
        System.out.println("线程一:"+s);
    },"t1").start();
    new Thread(()->{
        String s = "T2";
        s =  exchanger.exchange(s);
        System.out.println("线程二:"+s);
    },"t2").start();
}

7. LockSupport

API介绍

  • park(): 阻塞当前线程,直到unpark方法被调用或当前线程被中断,park方法才会返回。
  • parkNanos(long nanos): 同park方法,nanos表示最长阻塞超时时间,超时后park方法将自动返回。单位是纳秒 ,1秒 = 100010001000 纳秒
  • parkUntil(long deadline): 同park()方法,deadline参数表示最长阻塞到某一个时间点,当到达这个时间点,park方法将自动返回。(该时间为从1970年到现在某一个时间点的毫秒数)
  • unpark(Thread thread): 唤醒处于阻塞状态的线程thread。
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {

        System.out.println("主线程开启。。");

        Thread thread = new Thread(() -> {
            System.out.println("启动子线程");
            //阻塞当前线程
            LockSupport.park();
            System.out.println("子线程结束");
        });
        thread.start();

        // 主线程3S后解除子线程阻塞
        TimeUnit.SECONDS.sleep(3);
        LockSupport.unpark(thread);
        System.out.println("主线程结束");

    }

说明

  • 本类底层是使用 UNSAFE 类进行线程阻塞的, UNSAFE是Java中提供的可以直接操作系统底层的不安全的类,UNSAFE类要慎用。
  • 有很多带blocker 参数的重载方法,该参数主要用于问题排查和系统监控,在线程dump中会显示该参数的信息,有利于问题定位。
  • 也可以先调用unpark,再调用park方法,不过park 方法不会被阻塞。
  • 在同步代码块中使用park 方法时,线程不会释放锁,所以避免混合使用,以免出现死锁。
  • park方法支持中断,也就是说一个线程调用park方法进入阻塞后,如果该线程被中断则能够解除阻塞立即返回。但需要注意的是,它不会抛出中断异常,所以我们不必去捕获InterruptedException。
  • 27
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值