JUC的常见类

简介: JUC的常见类



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<V> 其中泛型参数表示返回值的类型

Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int ret = 0;
                for (int i = 1; i <= 100; i++) {
                    ret += i;
                }
                return ret;
            }
        };

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

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

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

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

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

FutureTask<V> 泛型参数表示结果的类型

此时在主线程中调用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<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int ret = 0;
                for (int i = 1; i <= 100; i++) {
                    ret += i;
                }
                return ret;
            }
        };
        FutureTask<Integer> 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("所有任务都已执行完成");
    }
}
目录
相关文章
|
2月前
|
并行计算 Java 开发者
JUC工具类: CyclicBarrier详解
`CyclicBarrier`是并发编程领域一个非常实用的同步辅助类,适用于并行任务场景,它提供了一种简便的线程同步机制。正确地理解和使用这个工具,对开发者来说,可以大大简化并行处理逻辑的复杂度,增强代码的健壮性与可维护性。
21 0
|
存储 安全 算法
一天一个 JUC 工具类 -- 并发集合
使用JUC工具包中的并发集合,我们可以避免手动处理锁和同步的复杂性,从而降低出现线程安全问题的概率。这些并发集合通过内部采用高效的算法和数据结构来优化并发操作,从而提供更好的性能和扩展性。
|
安全 Java
Java中的线程同步与同步器
Java中的线程同步与同步器
166 1
|
人工智能 移动开发 Java
【Java基础】线程同步类 CountDownLatch
CountDownLatch是JDK提供的一个同步工具,它可以让一个或多个线程等待,一直等到其他线程中执行完成一组操作。 CountDownLatch 基于AQS构建同步器: AQS - AbstractQueuedSynchronizer ,即抽象的队列同步器,是一种用来**构建锁和同步器**的框架。
|
存储 安全 Java
JUC并发编程(JUC核心类、TimeUnit类、原子操作类、CASAQS)附带相关面试题
1.JUC并发编程的核心类,2.TimeUnit(时间单元),3.原子操作类,4.CAS 、AQS机制
61 0
|
程序员 Java 安全
【JUC基础】03. 几段代码看懂synchronized
程序员经常听到“并发锁”这个名词,而且实际项目中也确实避免不了要加锁。那么什么是锁?锁的是什么?今天文章从8个有意思的案例,彻底弄清这两个问题。
250 0
【JUC基础】03. 几段代码看懂synchronized
|
存储 安全 Java
一天一个 JUC 工具类 -- AQS
AbstractQueuedSynchronizer(AQS)是Java中用于构建锁和同步器的抽象基类。它是Java并发工具包(java.util.concurrent)中实现高级线程同步控制的关键组件之一。AQS提供了一种基于等待队列的同步器框架,允许开发者构建自定义的同步器。在这篇文章中我们将从源码分析和底层原理的角度来介绍AQS。
|
人工智能 移动开发 Java
【Java基础】AQS (AbstractQueuedSynchronizer) 抽象队列同步器
AQS 是一个相对底层的同步器框架,对于一些常见的同步需求,Java 并发库已经提供了许多高级封装,如 ReentrantLock、ReadWriteLock、Semaphore 等,这些高级封装已经为我们提供了更简单易用的接口和功能。因此,在应用开发中,直接使用 AQS 的场景相对较少,更多的是通过使用它的子类来实现具体的同步机制。
|
消息中间件 资源调度 Java
【JUC基础】01. 初步认识JUC
前段时间,有朋友跟我说,能否写一些关于JUC的教程文章。本来呢,JUC也有在我的专栏计划之内,只是一直都还没空轮到他,那么既然有这样的一个契机,那就把JUC计划提前吧。那么今天就重点来初步认识一下什么是JUC,以及一些基本的JUC相关基础知识。
162 0
【JUC基础】01. 初步认识JUC
|
安全 Java 程序员
【JavaEE】Callable接口(NO.6线程创建方法)-JUC的常见类-与线程安全有关集合类
JavaEE & Callable接口(NO.6线程创建方法) & JUC的常见组件 & 与线程安全有关类和集合类
47 0