在访问共享资源之前进行加锁操作,在访问完成之后进行解锁操作。按照“是否允许在同一时刻被多个线程持有”来区分,锁可以分为共享锁与独占锁。
独占锁:独占锁也叫排他锁、互斥锁、独享锁,是指锁在同一时刻只能被一个线程所持有。一个线程加锁后,任何其他试图再次加锁的线程都会被阻塞,直到持有锁线程解锁。通俗来说,就是共享资源某一时刻只能有一个线程访问,其余线程阻塞等待。
如果是公平地独占锁,在持有锁线程解锁时,如果有一个以上的线程在阻塞等待,那么最先抢锁的线程被唤醒变为就绪状态去执行加锁操作,其他的线程仍然阻塞等待。
Java中的Synchronized内置锁和ReentrantLock显式锁都是独占锁。
JUC中的共享锁包括Semaphore(信号量)、ReadLock(读写锁)中的读锁、CountDownLatch倒数闩。
共享锁Semaphore
Semaphore可以用来控制在同一时刻访问共享资源的线程数量,通过协调各个线程以保证共享资源的合理使用。Semaphore维护了一组虚拟许可,它的数量可以通过构造器的参数指定。线程在访问共享资源前必须调用Semaphore的acquire()方法获得许可,如果许可数量为0,该线程就一直阻塞。线程访问完资源后,必须调用Semaphore的release()方法释放许可。更形象的说法是:Semaphore是一个许可管理器。
Semaphore的主要方法:
代码语言:javascript复制(1)Semaphore(permits)
构造一个Semaphore实例,初始化其管理的许可数量为permits参数值。
(2)Semaphore(permits,fair)
构造一个Semaphore实例,初始化其管理的许可数量为permits参数值,以及是否以公平模式
(fair参数是否为true)进行许可的发放。Semaphore和ReentrantLock类似,Semaphore发放许可时
有两种模式:公平模式和非公平模式,默认情况下使用非公平模式。
(3)availablePermits()
获取Semaphore对象可用的许可数量。
(4)acquire()
当前线程尝试获取Semaphore对象的一个许可。此过程是阻塞的,线程会一直等待Semaphore发放一个
许可,直到发生以下任意一件事:
·当前线程获取了一个可用的许可。
·当前线程被中断,就会抛出InterruptedException异常,并停止等待,继续往下执行。
(5)acquire(permits)
当前线程尝试阻塞地获取permits个许可。此过程是阻塞的,线程会一直等待Semaphore发放permits
个许可。如果没有足够的许可而当前线程被中断,就会抛出InterruptedException异常并终止阻塞。
(6)acquireUninterruptibly()
当前线程尝试阻塞地获取一个许可,阻塞的过程不可中断,直到成功获取一个许可。
(7)acquireUninterruptibly(permits)
当前线程尝试阻塞地获取permits个许可,阻塞的过程不可中断,直到成功获取permits个许可。
(8)tryAcquire()
当前线程尝试获取一个许可。此过程是非阻塞的,它只是进行一次尝试,会立即返回。
如果当前线程成功获取了一个许可,就返回true;如果当前线程没有获得许可,就返回false。
(9)tryAcquire(permits)
当前线程尝试获取permits个许可。此过程是非阻塞的,它只是进行一次尝试,会立即返回。
如果当前线程成功获取了permits个许可,就返回true;如果当前线程没有获得permits个许可,就返回false。
(10)tryAcquire(timeout,TimeUnit)
限时获取一个许可。此过程是阻塞的,会一直等待许可,直到发生以下任意一件事:
·当前线程获取了一个许可,则会停止等待,继续执行,并返回true。
·当前线程等待timeout后超时,则会停止等待,继续执行,并返回false。
·当前线程在timeout时间内被中断,则会抛出InterruptedException异常,并停止等待,继续执行。
(11)tryAcquire(permits,timeout,TimeUnit)
与tryAcquire(timeout,TimeUnit)方法在逻辑上基本相同,不同之处在于:在获取许可的数量上不同,
此方法用于获取permits个许可。
(12)release()
当前线程释放一个可用的许可。
(13)release(permits)
当前线程释放permits个可用的许可。
(14)drainPermits()
当前线程获得剩余的所有可用许可。
(15)hasQueuedThreads()
判断当前Semaphore对象上是否存在正在等待许可的线程。
(16)getQueueLength()
获取当前Semaphore对象上正在等待许可的线程数量。
代码语言:javascript复制public class SemaphoreTest
{
@org.junit.Test
public void testShareLock() throws InterruptedException
{
// 排队总人数(请求总数)
final int USER_TOTAL = 10;
// 可同时受理业务的窗口数量(同时并发执行的线程数)
final int PERMIT_TOTAL = 2;
// 线程池,用于多线程模拟测试
final CountDownLatch countDownLatch =
new CountDownLatch(USER_TOTAL);
// 创建信号量,含有两个许可
final Semaphore semaphore = new Semaphore(PERMIT_TOTAL);
AtomicInteger index = new AtomicInteger(0);
// 创建Runnable可执行实例
Runnable r = () ->
{
try
{
//阻塞开始获取许可
semaphore.acquire(1);
//获取了一个许可
Print.tco( DateUtil.getNowTime()
", 受理处理中...,服务号: " index.incrementAndGet());
//模拟业务操作: 处理排队业务
Thread.sleep(1000);
//释放一个信号
semaphore.release(1);
} catch (Exception e)
{
e.printStackTrace();
}
countDownLatch.countDown();
};
//创建10个线程
Thread[] tArray = new Thread[USER_TOTAL];
for (int i = 0; i < USER_TOTAL; i )
{
tArray[i] = new Thread(r, "线程" i);
}
//启动10个线程
for (int i = 0; i < USER_TOTAL; i )
{
tArray[i].start();
}
countDownLatch.await();
}
}
共享锁CountDownLatch
CountDownLatch是一个常用的共享锁,其功能相当于一个多线程环境下的倒数门闩。CountDownLatch可以指定一个计数值,在并发环境下由线程进行减一操作,当计数值变为0之后,被await方法阻塞的线程将会唤醒。通过CountDownLatch可以实现线程间的计数同步。
代码语言:javascript复制class Driver
{
private static final int N = 100; // 乘客数
public static void main(String[] args) throws InterruptedException
{ //step1:创建倒数闩,设置倒数的总数
CountDownLatch doneSignal = new CountDownLatch(N);
//取得CPU密集型线程池
Executor e = ThreadUtil.getCpuIntenseTargetThreadPool();
for (int i = 1; i <= N; i) // 启动报数任务
e.execute(new Person(doneSignal, i));
doneSignal.await(); //step2:等待报数完成,倒数闩计数值为0
Print.tcfo("人到齐,开车"); }
static class Person implements Runnable
{
private final CountDownLatch doneSignal;
private final int i;
Person(CountDownLatch doneSignal, int i)
{
this.doneSignal = doneSignal;
this.i = i;
}
public void run()
{
try
{
//报数
Print.tcfo("第" i "个人已到");
doneSignal.countDown(); //step3:倒数闩减少1
} catch (Exception ex)
{
}
}
}
}
(1)创建倒数闩,初始化CountDownLatch时设置倒数的总次数,比如为100。
(2)等待线程调用倒数闩的await()方法阻塞自己,等待倒数闩的计数器数值为0(倒数线程全部执行结束)。
(3)倒数线程执行完,调用CountDownLatch.countDown()方法将计数器数值减一。