JUC常见类的使用

JUC(java.util.concurrent)是Java提供的一个并发编程的工具包,里面包含了很多在并发环境下好用的工具,本篇主要来介绍JUC里几个常用的工具。

目录

Callable接口

 ReentrantLock

信号量Semaphore

CountDownLatch


Callable接口

在我们前面介绍的创建线程的方式中,无论是继承Thread类还是实训Runnable接口,都是通过重写run方法来定义线程所要执行的认为,但通过run方法我们无法获取线程执行完毕后的返回值,那如果遇到需要获取返回值的场景时该怎么办呢?此时就得考虑使用Callable接口了。接下来,我们具体来看一下Callable的使用方法。

通过自定义的一个实现Callable接口的类,我们可以发现,实现Callable需要重写call方法

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return null;
    }
}

call方法和run方法大体相同,都是描述线程具体要执行的认为,但是call方法是有返回值的,返回值的类型可以通过修改Callable的泛型参数来做出调整。

Callable并不能直接作为构建Thread类的参数,构建Thread类我们需要先创建另外一个类FutureTask,然后通过FutureTask来创建Thread,具体代码如下:

public void test01(){
        //以匿名内部类的方式创建Callable的实现类
        Callable<Integer> callable  = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //返回1
                return 1;
            }
        };
        //将callable传入FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        //将futuretask传入Thread
        Thread t = new Thread(futureTask);
    }

在FutureTask中有一个get方法,通过get方法就能获取到线程返回的参数,不过要注意的是在调用get方法时,会抛出一个打断异常,这是因为如果相关线程还没有执行完,当前线程就会进行阻塞等待,直到相关线程执行完毕并返回数据,当前线程才能继续执行并拿到返回的数据。完善代码后如下:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public void test01() throws ExecutionException, InterruptedException {
        //以匿名内部类的方式创建Callable的实现类
        Callable<Integer> callable  = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int i =1 ;
                //返回i
                System.out.println("线程t返回了数据:" + i);
                return i;
            }
        };
        //将callable传入FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        //将futuretask传入Thread
        Thread t = new Thread(futureTask);
        t.start();
        int i  = futureTask.get();
        System.out.println("主线程获得了返回数据:"+ i);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Test test = new Test();
        test.test01();
    }

}

运行结果

 ReentrantLock

ReenTrantLock是Java标准库为我们提供的另一把可重入锁,它和synchronize最大的差异就是加锁方式的不同,ReenTrantLock是基于代码进行加锁解锁的,需要自己通过写代码来解释和加锁,而synchronize则是基于代码块来加锁解锁的,进代码块自动加锁,出代码块自动解锁,无需手动写代码。ReenTrantLock中主要有以下几种方法

方法作用
lock加锁
trylock(time)有最大时间的加锁
unlock解锁

使用时通常以下面这种方式

ReentrantLock lock = new ReentrantLock();
        lock.lock();
        //要加锁的代码
        lock.unlock();

这里的lock对象就是进行加锁的对象

使用这种方式其实是有一定安全隐患的,如果在加锁的代码中出现异常,就会导致代码还没有执行到unlock方法就提前结束了,为了避免这个问题我们通常需要在代码外面再加一层try-catch,并将unlock放在finally中具体如下:

   ReentrantLock lock = new ReentrantLock();
       try {
           lock.lock();
           //代码
       }
       catch (Exception e){
           
       }
       finally {
           lock.unlock();
       }

ReentrantLock是可以实现公平锁的只需要在创建时将参数设为trure即可

  ReentrantLock lock = new ReentrantLock(true);

在synchronize中唤醒线程时只能随机唤醒,而使用ReentrantLock 则可以通过搭配Condition类来实现指定线程唤醒。

信号量Semaphore

信号量可以理解成一个计数器,他表示当前可以资源的数量,他有两个操作

  • P操作:申请一个资源
  • V操在:释放一个资源

信号量就像是在停车场经常能看到的当前剩余车位数量的告示牌,当有车子进来时,告示牌上的数字就减一(P操作),当有车子出来时,告示牌上的数字就加一(V操作),如果当前车位为0则不能再进来车(P操作阻塞),如果当前车库里没车(即信号l量是满的),则不能再出来车(V操作阻塞)

在Java中,Semaphore类对信号量进行了封装,其中acquire方法就对应了P操作,release方法就对应了V操作,接下来让我们通过Semaphore类来模拟一下前面的停车场景

import java.util.concurrent.Semaphore;

public class SemaphoreTest {
    public void test01(){
        //创建Semaphore类,并设置大小,表示停车场一共有多少车位,这里设置10个
        Semaphore semaphore = new Semaphore(10);
        //创建认务Runnable,表示一辆汽车出入停车场的过程
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                //进入停车场
                    System.out.println("进入车库,车位减少一个,车位数为"+ semaphore.getQueueLength());
                    semaphore.acquire();
                //停车
                    Thread.sleep(1000);
                    //出停车场
                    System.out.println("出车库,车位增加一个车位数为:"+ semaphore.getQueueLength());
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //创建20个线程,分别模拟20辆车,让这20辆车执行上面的任务
        for (int i = 0; i < 20; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
    }

    public static void main(String[] args) {
        SemaphoreTest test = new SemaphoreTest();
        test.test01();
    }
}

运行结果

使用Semaphore还能模拟实现一个简单的锁,将信号量初始值设为1,如果信号量为1,则代表无人加锁,如果为0则代表被加锁 代码如下

 Semaphore semaphore = new Semaphore(1);
    public void lock() throws InterruptedException {
        semaphore.acquire();
    }
    public void unlock(){
        semaphore.release();
    }

CountDownLatch

赛跑是一种常见体育竞赛,在一次赛跑中,会先设定好参赛人数,比赛开始后,当所有参赛人员都到冲线后,一场赛跑就结束了。CountDownLatch的使用其实就跟赛跑一样,也是先设定好一个数值,当进行设定的数值次冲线后,CountDownLatch就结束了。CountDownLatch有如下几个方法

方法作用
CountDownLatch(人数)构造方法,参数为参赛人数
await()进入阻塞等待,直到所有选手冲线
countDown()表示一个选手冲线

下面我们来具体使用一下CountDownLatch

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {
    //创建CountDownLatch,设置参赛人数为5
    CountDownLatch countDownLatch = new CountDownLatch(5);
    public void test01() throws InterruptedException {
        //设置五个线程代表五个参赛选手
        for (int i = 0; i < 5; i++) {
            int j = i;
            Thread t = new Thread(() ->{
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("选手" + j + "冲线");
                countDownLatch.countDown();
            });
            t.start();
        }
        //等待所有选手冲线
        countDownLatch.await();
        System.out.println("所有选手已冲线,比赛结束");
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatchTest test = new CountDownLatchTest();
        test.test01();
    }
}

运行结果:

CountDownLatch还可以应用到文件下载的场景里,将要下载的内容分成多个部分,然后创建多个线程同时下载,这样下载的速度就能成倍提升,通过CountDownLatch就能判断是否所有线程都下好了,如果都下好了,就被每个线程下载的整合在一起。

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值