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
操作,释放资源.计数器+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) 的常见类_第2张图片
我用具体的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) 的常见类_第3张图片
可以看到,主线程调用 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>() {
            @Override
            public 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() 方法来阻塞等待任务的执行结果。

你可能感兴趣的:(多线程,java)