[整理] Java 并发编程之 java.util.concurrent 包下常见类的使用
一,Condition
一个场景,两个线程数数,同时启动两个线程,线程A数1、2、3,然后线程B数4、5、6,最后线程A数7、8、9,程序结束,这涉及到线程之间的通信。
public class ConditionTest {
static class NumberWrapper {
public int value = 1;
}
public static void main(String[] args) {
//初始化可重入锁
final Lock lock = new ReentrantLock();
//第一个条件当屏幕上输出到3
final Condition reachThreeCondition = lock.newCondition();
//第二个条件当屏幕上输出到6
final Condition reachSixCondition = lock.newCondition();
//NumberWrapper只是为了封装一个数字,一边可以将数字对象共享,并可以设置为final
//注意这里不要用Integer, Integer 是不可变对象
final NumberWrapper num = new NumberWrapper();
//初始化A线程
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
//需要先获得锁
lock.lock();
System.out.println("ThreadA获得lock");
try {
System.out.println("threadA start write");
//A线程先输出前3个数
while (num.value <= 3) {
System.out.println(num.value);
num.value++;
}
//输出到3时要signal,告诉B线程可以开始了
reachThreeCondition.signal();
} finally {
lock.unlock();
System.out.println("ThreadA释放lock");
}
lock.lock();
try {
//等待输出6的条件
System.out.println("ThreadA获得lock");
reachSixCondition.await();
System.out.println("threadA start write");
//输出剩余数字
while (num.value <= 9) {
System.out.println(num.value);
num.value++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("ThreadA释放lock");
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("ThreadB获得lock");
Thread.sleep(5000);//是await方法释放了锁
while (num.value <= 3) {
//等待3输出完毕的信号
reachThreeCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("ThreadB释放lock");
}
try {
lock.lock();
System.out.println("ThreadB获得lock");
//已经收到信号,开始输出4,5,6
System.out.println("threadB start write");
while (num.value <= 6) {
System.out.println(num.value);
num.value++;
}
//4,5,6输出完毕,告诉A线程6输出完了
reachSixCondition.signal();
} finally {
lock.unlock();
System.out.println("ThreadB释放lock");
}
}
});
//启动两个线程
threadB.start();
threadA.start();
}
}
创建方式:通过Lock创建,Lock.newCondition();
常用方法:
- await():阻塞,直到相同的Condition调用了signal方法。
- signal():通知。
总结:Condition必须与Lock一起使用(wait()、notify()必须与synchronized一起使用,否则运行会报错java.lang.IllegalMonitorStateException),相比于wait与notify更加的灵活,可以设置各种情形,如上例中的到达3和到达6两个条件。
执行结果:
二,CountDownLatch
public class CountDownLatchTest {
public static void main(String[] args) {
final CountDownLatch c = new CountDownLatch(3);//总数3
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
try {
System.out.println("开始等");
c.await();//阻塞,等待countDown,当countDown到0就执行后面的完事了
System.out.println("完事");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
for(int i=3;i>0;i--){
c.countDown();//减1
System.out.println(i);
}
}
});
t1.start();
t2.start();
}
}
创建方式:直接创建,new CountDownLatch(int num);
常用方法:
- await():阻塞,直到countDown方法被执行了num次。
- countDown():减
总结:适用于一个线程等待其他线程的情景。
执行结果:
开始等
3
2
1
完事
三,CyclicBarrier
与CountDownLatch有什么区别?
- CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。正如上例,只有5个人全部跑到终点,大家才能开喝,否则只能全等着。
- CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成。
另外:
- CountDownLatch减计数,CyclicBarrier加计数。
- CountDownLatch是一次性的,CyclicBarrier可以重用。
public class MainMission {
private CyclicBarrier barrier;
private final static int threadCounts = 5;
public void runMission() {
ExecutorService exec=Executors.newFixedThreadPool(threadCounts);
//new 的时候要传入数字,我发现,这个类似semaphore,如果位置不足,线程会抢位置。数字要是threadCounts+1为主线程留一个位子,但实际测试中发现,只要等于threadCount就可以
barrier=new CyclicBarrier(threadCounts+1);
for(int i=0;i<5;i++){
exec.execute(new Mission(barrier));
}
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有任务都执行完了");
exec.shutdown();//如果不关闭,程序一直处于运行状态
}
public static void main(String[] args) {
MainMission m = new MainMission();
m.runMission();
}
}
class Mission implements Runnable{
private CyclicBarrier barrier;
public Mission(CyclicBarrier barrier){
this.barrier = barrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行任务");
try {
int sleepSecond = new Random().nextInt(10)*1000;
System.out.println(Thread.currentThread().getName()+"要执行"+sleepSecond+"秒任务");
Thread.sleep(sleepSecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis());
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}
创建方式:直接创建,new CyclicBarrier(int num);
常用方法:
- await():阻塞,直到阻塞的线程数量达到num个。
总结:想想一下百米跑,所有运动员都就位之后才会发令起跑,线程调用await意味着说,我准备好了。
执行结果:
pool-1-thread-1开始执行任务
pool-1-thread-3开始执行任务
pool-1-thread-2开始执行任务
pool-1-thread-3要执行0秒任务
pool-1-thread-1要执行4000秒任务
pool-1-thread-2要执行7000秒任务
pool-1-thread-4开始执行任务
pool-1-thread-5开始执行任务
pool-1-thread-4要执行4000秒任务
pool-1-thread-5要执行6000秒任务
1608713428706
1608713428706
pool-1-thread-4执行完毕
1608713428706
pool-1-thread-5执行完毕
1608713428706
pool-1-thread-1执行完毕
1608713428706
pool-1-thread-3执行完毕
pool-1-thread-2执行完毕
所有任务都执行完了
四,Semaphore
下面是一个上厕所的例子,厕所位置有限,想用得排队了。实现使用的就是信号量,可以看出信号量可以用来做限流。
public class MySemaphore implements Runnable{
Semaphore position;
private int id;
public MySemaphore(int i,Semaphore s){
this.id=i;
this.position=s;
}
@Override
public void run() {
try{
if(position.availablePermits()>0){
System.out.println("顾客["+this.id+"]进入厕所,有空位");
}
else{
System.out.println("顾客["+this.id+"]进入厕所,没空位,排队");
}
position.acquire();//只有在acquire之后才能真正的获得了position
System.out.println("#########顾客["+this.id+"]获得坑位");
Thread.sleep((int)(Math.random()*100000));
System.out.println("@@@@@@@@@顾客["+this.id+"]使用完毕");
position.release();
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String args[]){
ExecutorService list=Executors.newCachedThreadPool();
Semaphore position=new Semaphore(2);
for(int i=0;i<10;i++){
list.submit(new MySemaphore(i+1,position));
}
list.shutdown();
position.acquireUninterruptibly(2);
System.out.println("使用完毕,需要清扫了");
position.release(2);
}
}
创建方式:直接创建,new Semaphore(int num);
常用方法:
- availablePermits():看现在可用的信号量。
- acquire():尝试获取一个位置,如果获取不到则阻塞。
- release():释放位置。
- acquireUninterruptibly(int num):尝试获取num个许可,如果没有足够的许可则阻塞,一直阻塞到有足够的许可释放出来。调用这个方法的线程具有优先获取许可的权利。如果调用线程被interrupted,该线程并不会被打断,它会继续阻塞等待许可。
总结:抢位置。
执行结果:
顾客[1]进入厕所,有空位
顾客[3]进入厕所,有空位
#########顾客[1]获得坑位
顾客[2]进入厕所,有空位
顾客[4]进入厕所,没空位,排队
#########顾客[3]获得坑位
顾客[5]进入厕所,没空位,排队
顾客[6]进入厕所,没空位,排队
顾客[7]进入厕所,没空位,排队
顾客[8]进入厕所,没空位,排队
顾客[9]进入厕所,没空位,排队
顾客[10]进入厕所,没空位,排队
@@@@@@@@@顾客[3]使用完毕
#########顾客[2]获得坑位
@@@@@@@@@顾客[1]使用完毕
#########顾客[4]获得坑位
@@@@@@@@@顾客[2]使用完毕
#########顾客[5]获得坑位
@@@@@@@@@顾客[5]使用完毕
#########顾客[6]获得坑位
@@@@@@@@@顾客[4]使用完毕
#########顾客[7]获得坑位
@@@@@@@@@顾客[6]使用完毕
#########顾客[8]获得坑位
@@@@@@@@@顾客[7]使用完毕
五,ReentrantLock
创建方式:
- new ReentrantLock(); 此种创建方式会创建出一个非公平锁。
- new ReentrantLock(true); 此种方式会创建出一个公平锁。
非公平锁:当锁处于无线程占有的状态,此时其他线程和在队列中等待的线程都可以抢占该锁。
公平锁:当锁处于无线程占有的状态,在其他线程抢占该锁的时候,都需要先进入队列中等待。
tryLock()方法:尝试去获取锁,如果没有获取到直接返回,不等待。
细节看这个吧,https://blog.csdn.net/jiangjiajian2008/article/details/52226189
六,ReentrantReadWriteLock
创建方式:new ReentrantReadWriteLock();
常用方法:
- readLock().lock();写锁
- writeLock().lock();读锁
- readLock().unlock();解锁
- writeLock().unlock();解锁
总结:
- 如果目前是读锁,其他读锁也可以进请求,写锁不能进。
- 如果目前是写锁,那么其他所有的锁都不可以进。
- 适用于读多写少的情况,如果是写多读少用ReentrantLock。
七,Callable接口
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取结果;当不调用此方法时,主线程不会阻塞!
与Runnable对比:
- Callable可以有返回值,Runnable没有
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
八,线程池
提供的线程池有几种:
//有数量限制的线程池
ExecutorService service=Executors.newFixedThreadPool(4);
//没有数量限制的线程池
ExecutorService service=Executors.newCachedThreadPool();
//单线程池
ExecutorService service=Executors.newSingleThreadExecutor();
他们都是通过下面这个线程池实现的
有数量线程池的实现方式
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads/*核心线程数*/,
nThreads/*最高线程数*/,
0L/*高出核心线程数的线程最高存活时间*/,
TimeUnit.MILLISECONDS/*高出核心线程数的线程最高存活时间单位*/,
new LinkedBlockingQueue<Runnable>()/*任务队列*/);
}