JUC的常见类

目录

Callable

ReentrantLock

Semaphore

CountDownLatch


JUC java.util.concurrent,其中存放了一些进行多线程编程时有用的类

Callable

Callable是一个接口,在我们实现Runnable创建线程时,Runnable关注的是其过程,而不关注其执行结果(其返回值为void),而Callable会关注执行结果,Callable提供了call方法,其返回值就是线程执行任务得到的结果

因此,当我们在编写多线程代码时,希望得到线程种代码的返回值时,就可以使用Callable

例如:一个线程需要实现 1 + 2 + 3 + ... + 100,并返回其结果

若我们使用实现Runnable的方式创建线程时:

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                int ret = 0;
                for (int i = 1; i <= 100; i++) {
                    ret += i;
                }
            }
        });
        t.start();
        t.join();
    }
}

若此时主线程需要获取到其计算结果,则需要使用一个成员变量来保存结果

public class Demo1 {
    private static int sum = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                int ret = 0;
                for (int i = 1; i <= 100; i++) {
                    ret += i;
                }
                sum = ret;//保存结果
            }
        });
        t.start();
        t.join();
        System.out.println(sum);
    }
}

而若我们使用Callable时:

Callable 其中泛型参数表示返回值的类型
        Callable callable = new Callable() {
            @Override
            public Integer call() throws Exception {
                int ret = 0;
                for (int i = 1; i <= 100; i++) {
                    ret += i;
                }
                return ret;
            }
        };

此时就不必引入额外的成员变量了,直接使用返回值即可

然而,Thread未提供构造函数来传入callable

JUC的常见类_第1张图片

此时,我们需要使用 FutureTask 来作为Thread和callable的“粘合剂”

将callable实例使用FutureTask包装一下,再在构造方法传入FutureTask:

此时线程就会执行FutureTask内部的Callable中的call方法,计算完成后,就将结果放入FutureTask对象中

FutureTask 泛型参数表示结果的类型

此时在主线程中调用FutureTask中的get()方法(带有阻塞功能),等待计算完毕,从FutureTask中获取到结果

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

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable callable = new Callable() {
            @Override
            public Integer call() throws Exception {
                int ret = 0;
                for (int i = 1; i <= 100; i++) {
                    ret += i;
                }
                return ret;
            }
        };
        FutureTask futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();

        //此时无需使用join,使用futureTask中的get()方法获取到结果
        System.out.println(futureTask.get());
    }
}

观察两种实现方式,我们可以发现:

使用Callable和FutureTask,使代码简化了很多,且不用使用成员变量来保存结果

Callable和Runnable相对,都是描述一个“任务”,Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务

Callable通常需要搭配FutureTask使用,FutureTask用来保存Callable的返回结果

ReentrantLock

ReentrantLock是一种可重入互斥锁,与synchronized类似,都是用来实现互斥效果,保证线程安全的

ReentrantLock的用法:

lock():加锁,若获取不到锁就死等

unlock():解锁

tryLock(超时时间):加锁,若获取不到锁,等待时间一定后就放弃加锁

当我们在使用ReentrantLock时,需要加锁和解锁两个操作,因此当我们在加锁后,很容易忘记解锁,例如在unlock之前,触发了异常 或 return语句,此时就可能不会执行unlock

因此,为了保证unlock的执行,我们可以将其放在finally中

例如:

ReentrantLock lock = new ReentrantLock();
lock.lock();
try{
    //执行代码
}finally {
    lock.unlock();
}

synchronzied使用时无需手动释放锁,ReentrantLock使用时需要手动释放,使用起来更加灵活,但也容易漏掉unlock 

为什么有了synchronized后,还有ReentrantLock?


1. ReentrantLock提供了tryLock操作。lock直接进行加锁,当加锁失败时,就阻塞,而tryLock尝试进行加锁,若加锁失败,等待一定时间后,不阻塞,直接放弃加锁,返回 false,表示加锁失败

2. ReentrantLock提供了公平锁的实现(遵循“先来后到”规则,即等待时间长的线程先获取到锁)。通过队列来记录加锁线程的先后顺序,ReentrantLock默认是非公平锁,在构造方法中传入true 则可设置为公平锁

3. 更强大的唤醒机制。synchronized,搭配wait和notify来实现等待唤醒,每次随机唤醒一个等待的线程;而ReentrantLock,搭配Condition类来实现通知等待机制,可以更精确控制唤醒某个指定的线程

Semaphore

信号量Semaphore,用来表示“可用资源的个数”,其本质就是一个计数器

当申请资源时,可用资源数 -1(信号的P操作)

 当释放资源时,可用资源数 +1(信号的V操作)

Semaphore中的加减计算操作都是原子的,可以在多线程环境下直接使用

信号量也是操作系统提供的机制,JVM对其对应的API进行封装,因此我们可用直接通过Java代码来调用这里的相关操作

Semaphore类中acquire()方法表示申请资源(P操作),release()方法表示释放资源(V操作)

例如:

public class Demo2 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(5);//有5个可用资源

        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println("申请资源");
                        semaphore.acquire();
                        System.out.println("获取到资源");
                        Thread.sleep(1000);
                        System.out.println("释放资源");
                        semaphore.release();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }
}

CountDownLatch

同时等待多个任务执行结束

例如,构造CountDownLatch实例latch,初始化n表示有n个任务需要完成。

在任务执行完毕时,调用countDown()方法,告知latch当前任务执行完毕,则CountDownLatch内部的计数器 -1

在主线程中调用latch.await(),阻塞等待所有任务都执行完毕(计数器为0)

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10);//此时有10个任务需要完成
        for (int i = 0; i < 10; i++) {
            int id = i;
            Thread t = new Thread(()->{
                Random random = new Random();
                int time = (random.nextInt(5) + 1) * 1000;//执行任务的时间
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(id + "任务执行结束");
                //告知latch执行结束
                latch.countDown();
            });
            t.start();
        }
        //通过await来等待所有任务执行结束
        latch.await();
        System.out.println("所有任务都已执行完成");
    }
}

你可能感兴趣的:(JavaEE,java,开发语言,多线程,java-ee)