JUC
JUC(Java并发包)是我们需要深入了解的一个包,我们来看看有哪些重要的内容吧。
ReentrantLock(可重入锁)
ReentrantLock可重入锁,可以实现公平锁(参数传递true即可)但要注意两
- 点lock一定要放在try之前
- 在finally中一定要释放锁
semaphore(信号量)
semaphore可以实现限流的功能,可以传递参数设置线程的个数,主要方法有两个
- acquire()尝试获取锁
- release()释放锁
public static void main(String[] args) {
// 创建信号量
Semaphore semaphore = new Semaphore(2, true);
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100));
for (int i = 0; i < 4; i++) {
// 创建任务
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
" 到达停车场");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// 尝试获取锁
semaphore.acquire();
// 当代码执行到此处说明已经获取到锁
System.out.println(Thread.currentThread().getName() +
" 进入停车场---------");
// 车辆停留从时间构建
int num = 1 + new Random().nextInt(5);
try {
Thread.sleep(num * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 离开停车场
System.out.println(Thread.currentThread().getName() +
" 离开停车场。。。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
semaphore.release();
}
}
});
}
}
CountDownLatch(计时器)
CountDownLatch等待所有的线程进入某个步骤后,在统一执行某个流程(就好像田径比赛,当所有选手都到达终点之后,再来宣布成绩),主要方法有一个
- awiat():每次线程到达await()阻塞时,计时器+1,直到计时器的数值达到CountDownLatch的个数时在执行下面的代码
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
for (int i = 1; i < 11; i++) {
final int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
" 开始起跑");
try {
Thread.sleep(finalI * 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" 到达终点");
// 将计数器 -1
latch.countDown();
}
}).start();
}
// 阻塞等待
latch.await();
System.out.println("所有人都到达终点了,公布排名");
}
CyclicBarrier(循环屏障)
CountDownLatch的缺点是只能用一次,之后便不可以再用了,而CyclicBarrier可以使用多次, CyclicBarrier内有一个计数器,每次线程执行到await方法时计数器+1,知道计数器的值等于设置的值时,就会突破屏障执行后面的代码,(在突破屏障之后计数器重置清零可以进行下一轮的判断)
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("执行了 CyclicBarrier 里面的 Runnable");
}
});
for (int i = 1; i < 11; i++) {
int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ " 开始起跑");
try {
Thread.sleep(finalI * 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName()
+ " 等待其他人---------");
// 计数器 -1,判断计时器是否为 0
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
// 代码执行带此行,说明已经有一组线程已经满足条件了
System.out.println(Thread.currentThread().getName()
+ " 执行结束。。。。。。");
}
}).start();
}
}
ConcurrentHashMap
HashMap:非安全的容器,在多线程的情况下不安全
- JDK1.7头插法会造成死循环
- JDK1.8尾插法会造成数据覆盖的问题
ConcurrentHashMap的实现原理
ConcurrentHashMap在 JDK 1,7 和 JDK1.8的实现方式是不同的
- JDK1.7中ConcurrentHashMap是由Segment和HashEntry组成的(即ConcurrentHashMap把哈希桶切分成小数组(Segment),每个小数组由n个HashEntry组成),
其中Segment继承ReentrantLock,是一种可重入锁,扮演锁的角色,HashEntry用于存储键值对数据。
JDK1.8中ConcurrentHashMap选择了与HashMap相同的数组+ 链表+红黑树的结构,在锁的实现上抛弃了原有的Segment分段锁,而采用了CAS+synchronized的实现,降低了锁的粒度,提高了并发
HashTable也是线程安全的,但是给对象整体加了锁导致效率不高,不推荐使用。