> 文章列表 > JUC(java.util.concurrent) 的常见类

JUC(java.util.concurrent) 的常见类

JUC(java.util.concurrent) 的常见类

文章目录

  • 前言
  • 一.ReentrantLock
  • 二.原子类
  • 三.信号量 Semaphore
  • 四.CountDownLatch
  • 五.Callable 接口

前言

一.ReentrantLock

ReentrantLock 是 Java 中一个提供同步机制的类,用于控制对共享资源的访问。它实现了 Lock 接口,提供了一组方法来获取和释放共享资源的锁.
从这里可以看出来reentrantLock和Synchronized在功能上是不是有些相似呢?
我们可以来简单的看一下.
从四个方面出发:

我们先从四个方面去说明
1.sychronized只是加锁和解锁,加锁的时候如果发现锁被占用,只能阻塞等待.
ReentrantLock还提供一个tryLock的方法,如果加锁成果,没啥特殊反应
如果加锁失败,不会阻塞,直接返回FALSE.

2.synchronized关键字,是基于代码块的方式来控制加锁解锁的
ReentrantLock则是提供了lock 和 unlock 独立的方法,来进行加锁解锁~

3.sychronized是一个非公平锁(概率均等,不遵守先来后到)
ReentrantLock提供 公平和非公平的俩种工作模式.(在构造方法中,传入true开启公平锁)

4.synchronized 搭配wait notify 进行等待唤醒,如果多个线程wait同一对象,notify的时候是随机唤醒一个.
ReentrantLock则是搭配Condition这个类.这个类也能起到等待通知,可以功能更强大.


二.原子类

原子类内部用的是 CAS 实现,所以性能要比加锁实现 i++ 高很多。原子类有以下几个
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicReference
AtomicStampedReference

以 AtomicInteger 举例,常见方法有
addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i–;
incrementAndGet(); ++i;
getAndIncrement(); i++;


三.信号量 Semaphore

信号量, 用来表示 “可用资源的个数”. 本质上就是一个计数器.
Semaphore 维护了一个计数器,表示可用的许可证数量。当线程需要访问某个共享资源时,需要先获取一个许可证,如果许可证数量为零,则线程需要等待,直到有其他线程释放了许可证,才能获取到许可证并访问该共享资源。
举一个实际的停车场例子吧
JUC(java.util.concurrent) 的常见类

信号量,本质上是一个计数器.描述了当前"可用资源"的个数.Р操作,申请资源.计数器–1
操作,释放资源.计数器+1
如果计数器已经是0了,继续申请资源,就会阻塞等待!!
了解了概念之后,我们直接来看一看代码
Semaphore 维护了一个计数器,表示可用的许可证数量。当线程需要访问某个共享资源时,需要先获取一个许可证,如果许可证数量为零,则线程需要等待,直到有其他线程释放了许可证,才能获取到许可证并访问该共享资源。

Semaphore 的主要方法包括:

1.acquire() 方法:获取一个许可证,如果没有许可证可用,则当前线程会被阻塞。

2.release() 方法:释放一个许可证,使其可供其他线程获取。

Semaphore semaphore = new Semaphore(3); // 最多允许 3 个线程同时访问Runnable task = () -> {try {semaphore.acquire(); // 获取许可证// 访问共享资源的代码} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(); // 释放许可证}
};Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
Thread t3 = new Thread(task);t1.start();
t2.start();
t3.start();

这里创建了三个线程,并启动它们,这三个线程会依次获取许可证并访问共享资源。由于许可证数量为 3,因此同时只有最多三个线程能够访问该共享资源,其他线程需要等待其他线程释放许可证才能继续执行.


四.CountDownLatch

同时等待 N 个任务执行结束.
可能大家不是很明白,我这里用一个例子,来帮大家去说明.
JUC(java.util.concurrent) 的常见类
我用具体的java代码来模拟这个场景
假设有三个线程需要执行某个任务,而某个主线程需要等待三个线程全部执行完毕后再进行下一步操作:

import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(3); // 计数器初始值为 3// 定义线程执行的任务Runnable task = () -> {System.out.println(Thread.currentThread().getName() + " 执行任务");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 任务执行完毕");latch.countDown(); // 计数器减一};// 创建三个线程并启动它们Thread t1 = new Thread(task, "Thread 1");Thread t2 = new Thread(task, "Thread 2");Thread t3 = new Thread(task, "Thread 3");t1.start();t2.start();t3.start();// 主线程等待所有线程执行完毕latch.await();System.out.println("所有线程执行完毕,进行下一步操作");}
}

JUC(java.util.concurrent) 的常见类
可以看到,主线程调用 latch.await() 方法等待三个线程执行完毕。当三个线程都执行完毕后,主线程才会继续执行,输出 “所有线程执行完毕,进行下一步操作”。这个例子中,每个线程执行任务后都会调用 latch.countDown() 方法将计数器减一。当计数器减为 0 时,主线程被唤醒并继续执行。


五.Callable 接口

Callable 是一个 interface . 相当于把线程封装了一个 “返回值”. 方便程序猿借助多线程的方式计算结果.

我这里直接用代码来介绍一下具体的用法.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class ThreadDemo31 {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建任务Callable<Integer>callable=new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum=0;for (int i=0;i<=100;i++){sum+=i;}return sum;}};//还需要找个人,来完成这个任务(线程)FutureTask<Integer> futureTask =new FutureTask<>(callable);Thread t=new Thread(futureTask);t.start();System.out.println(futureTask.get());}
}

这里对这个方法进行以下的说明:
1.代码中首先创建了一个实现 Callable 接口的匿名类,并在其中实现了 call() 方法来计算从 0 到 100 的整数之和,并返回结果.

2.然后,代码创建了一个新的线程对象 t,并将 FutureTask 对象 futureTask 作为 t 线程的参数传递,这样就将任务提交给了一个新的线程执行。

3.最后,代码调用了 futureTask.get() 方法来阻塞等待任务执行的结果,并将结果打印到控制台上。

总之,这段代码演示了如何使用 Callable 接口和 FutureTask 类来实现多线程编程,并获取任务的执行结果。使用 FutureTask 对象可以将 Callable 对象封装成一个 Runnable 对象,然后将其提交给一个新的线程执行,并通过 get() 方法来阻塞等待任务的执行结果。