J.U.C包的同步工具类
LockSupport
这个类内部没什么逻辑,实际上他是使用Unsafe的本地方法来完成操作,主要使用的Unsafe方法有下面两个:
public native void park(boolean isAbsolute, long time);
public native void unpark(Object obj);
常用的方法有两个:
LockSupport.park();
LockSupport.unpark(Thread t);
传统的方法如果notify()方法在wait()方法之前执行,则线程无法被唤醒。但如果调用park()方法之前调用了unpark()或者线程中断,则park直接返回,不会挂起,反之则park会挂起当前线程。所以即便unpark()先于park()调用,也能实现唤醒的效果。
下面展示了unpark先于park执行的情况。
public class JUCTest {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try {
//先睡眠3秒,确保unpark先执行
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我要睡觉");
LockSupport.park();
System.out.println("我醒了");
});
Thread t2 = new Thread(()->{
LockSupport.unpark(t1);
System.out.println("唤醒你");
});
t1.start();
t2.start();
}
}
Semaphore
信号量,它提供了访问同一份资源的并发量的控制,也是有公平锁、非公平锁之分。当Semaphore的构造方法传入的perimits参数为1时,他就是一把互斥锁。和互斥锁的原理差不多,也是基于AQS实现。
public class JUCTest {
private static final Semaphore SEMAPHORE=new Semaphore(3);
public static void main(String[] args) {
for (int i=0;i<5;i++){
new Thread(()->{
boolean b = SEMAPHORE.tryAcquire();
if (!b){
System.out.println("不许可进入,线程id:"+Thread.currentThread().getId());
return;
}
try {
System.out.println("许可进入,线程id:"+Thread.currentThread().getId());
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
SEMAPHORE.release();
System.out.println("成功退出,线程id:"+Thread.currentThread().getId());
}
}).start();
}
}
}
CountDownLatch
闭锁,它可以使一批线程在某一处等待,直到这些线程执行完相应操作后,闭锁打开,这些等待的线程才可以继续执行。
基于AQS实现,不过不区分公平锁和非公平锁,AQS在内部维护了一个state,当state!=0,调用await()方法的线程便会被放入AQS的阻塞队列,进入阻塞状态。通过countDown()方法一直累减state,减到0后一次性唤醒所有线程。
假设闭锁初始总数为M,有N个线程await(),则M个线程countDown()后,N个线程被唤醒。
public class JUCTest {
private static final CountDownLatch LATCH=new CountDownLatch(10);
private static final Random RANDOM=new Random();
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<10;i++){
new Thread(()->{
try {
int time = RANDOM.nextInt(10);
TimeUnit.SECONDS.sleep(time);
System.out.println("线程:"+Thread.currentThread().getId()+"执行完成,执行时间:"+time+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//计数减一
LATCH.countDown();
}
}).start();
}
LATCH.await();
System.out.println("所有线程已执行完成,结束程序");
}
}
CyclicBarrier
称为循环屏障,它的作用是使在其上等待的线程达到一定数量后再让它们继续往下执行。CyclicBarrier内部也有一个计数器,计数器的初始值在创建对象时通过构造参数指定,另外,在构造方法中还可以传入一个回调函数barrierAction,在最后一个执行await()的线程中调用,只执行一次。它基于ReentrantLock和Condition实现。
CyclicBarrier的计数器可以重置,可以重复使用。
CyclicBarrier 会响应中断,抛出异常,然后重置。
public class JUCTest {
private static final Random RANDOM=new Random();
private static final CyclicBarrier BARRIER =
new CyclicBarrier(10,()->{
System.out.println("完美运行");
});
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<10;i++){
new Thread(()->{
try {
int time = RANDOM.nextInt(10);
TimeUnit.SECONDS.sleep(time);
System.out.println("线程:"+Thread.currentThread().getId()+"执行完成,执行时间:"+time+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
BARRIER.await();
System.out.println("所有线程已执行完成,结束程序");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
Exchanger
Exchanger用于线程之间交换数据。当一个线程调用exchange准备和其他线程交换数据的时候,有两种情况:
一种是没有其他线程要交换数据,自己只能自旋或者阻塞,等待;
另一种是恰好有其他线程在等着交换,那么和对方交换。
下面的例子是4个线程使用同一个Exchanger交换数据,一次交换需要两个线程参与,交换完成则会解除阻塞。
public class JUCTest {
private static final Random RANDOM=new Random();
private static final Exchanger<Integer> EXCHANGER=new Exchanger<>();
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<4;i++){
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(RANDOM.nextInt(5));
int origin = RANDOM.nextInt(10);
Integer exchange = EXCHANGER.exchange(origin);
System.out.println("线程id:"+Thread.currentThread().getId()+",原有数据:"+origin+",获取数据:"+exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}