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 维护了一个计数器,表示可用的许可证数量。当线程需要访问某个共享资源时,需要先获取一个许可证,如果许可证数量为零,则线程需要等待,直到有其他线程释放了许可证,才能获取到许可证并访问该共享资源。
举一个实际的停车场例子吧
信号量,本质上是一个计数器.描述了当前"可用资源"的个数.Р操作,申请资源.计数器–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,因此同时只有最多三个线程能够访问该共享资源,其他线程需要等待其他线程释放许可证才能继续执行.
同时等待 N 个任务执行结束.
可能大家不是很明白,我这里用一个例子,来帮大家去说明.
我用具体的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("所有线程执行完毕,进行下一步操作");
}
}
可以看到,主线程调用 latch.await() 方法等待三个线程执行完毕。当三个线程都执行完毕后,主线程才会继续执行,输出 “所有线程执行完毕,进行下一步操作”。这个例子中,每个线程执行任务后都会调用 latch.countDown() 方法将计数器减一。当计数器减为 0 时,主线程被唤醒并继续执行。
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() 方法来阻塞等待任务的执行结果。