(1)可重入锁
1. ReentrantLock*
(1)lock写在try之前
(2)在 final 里面进行 unlock()
(2)信号量
2.信号量semaphore
可以实现限流功能
acquire():尝试获取锁,如果可以正常获取到,则执行后面的逻辑业务,如果获取失败,则阻塞等待
release():释放锁
信号量演示
*/
public class ThreadDemo1 {
public static void main(String[] args) {
//创建信号量
Semaphore semaphore = new Semaphore(2);
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100));
for (int i = 0; i < 4; i++) {
//创建任务1
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "到达停车场");
try {
Thread.sleep(1000);
//试图进入停车场
try {
//尝试获取锁
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
//当代码执行到此处说明已经获取到锁
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();
}
}
});
}
}
}
(3)计数器
3.计数器:CountDownLatch
作用:用来保障一组线程同时完成某个操作之后,才能继续完成后面的任务
await():当线程数量不满足CountDownLatch的数量的时候执行此代码会阻塞等待,直到数量满足之后,再执行await之后的代码。
题:CountDownLatch如何实现?
答:在CountDownLatch里面有一个计数器,计数器的数量在初始化的时候设置,每次调用CountDown()方法的时候,计数器数量-1,直到减到0之后,就可以执行await之后的代码了。
CountDownLatch缺点:CountDownLatch计数器的使用是一次性的,当用完一次之后就不能再使用了
**
* 计数器:CountDownLatch使用
*/
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
//声明计数器初始值为5
CountDownLatch countDownLatch = 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*100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"到达终点");
//将计数器-1
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println("公布排名");
}
}
(4)循环屏障
4.循环屏障(循环栅栏)cyclicBarrier
使用原理:它的内部有一个可以重复的计数器 count,每次执行 await 方法的时候计数器 -1,直到计数器为0时,执行await之后的方法,并且此时会将count值(计数器的值)重置继续利用。
cyclicBarrier.await()执行步骤
(1)计数器-1;
(2)判断计数器是否为0,如果为0执行之后的代码,如果不为0阻塞等待;
ps:当计数器为0时,首先会执行await之后的代码,将计数器重置。
* 循环栅栏
*/
public class ThreadDemo3 {
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();
}
System.out.println(Thread.currentThread().getName()+"等待其他人");
//计数器-1.判断计时器是否为0
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//代码执行到此行,说明已经有一组线程满足条件了
System.out.println(Thread.currentThread().getName()+"执行结束");
}
}).start();
}
}
}
题:CountDownLatch和CyclicBarrier区别?
答:CountDownLatch计数器只能使用一次,CyclicBarrier计数器能重复使用
安全容器
1.HashMap:非线程安全的容器.
产生问题:
JDK1.7:死循环
HashMap在JDK 1. 7使用头插法、JDK 1.8 尾插法。JDK1.8:数据覆盖
HashMap负载因子(加载因子) 0. 75扩容:
16*0. 75=进行扩容
0.75:尽量的避免哈希冲突所带来的性能开销问题(空间换时间的方案)。
死循环产生原因:
Entry[] src = table;
int newCapacity = newTable. Length;
for (int j=0;j <src.length;j++){ //数组
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
//循环链表
do{
Entry<K,V> next = e.next;//next指向e的下一个元素.
int i = indexFor(e .hash, newCapacity);//得 到当前元素e在新数组的位置
e.next = newTable[i];//e. next指向新元素
newTable[i] = e;//将当前元素e移动到新数组的位置
e = next;//e指向next
while (e . != nu1l) ;
HashMap线程安全问题解决方案:
2.ConcurrentHashMap
ConcurrentHashMap的实现原理:
JDK 1.7是将ConcurrentHashMap分成几个segment 进行加锁。悲观锁
JDK1.8锁优化,读的时候不加锁,写的时候加锁,使用了大量的CAS、Voiltail
3.Hashtable 安全容器:
给put方法整体加锁,因此一般情况下不会使用Hashtable.(整个方法添加 synchronized,性能比较低)
题:HashMap、 Hashtable,ConcurrentHashMap的区别:
答:
- HashMap是非线程安全的容器,它在JDK 1.7会造成死循环,JDK 1.8 会造成数据覆盖; Hashtable和ConcurrentHashMap都是线程安全的容器。
- Hashtable实现线程安全的手段比较简单,它是在put方法整体加了一把锁,使用 synchronized修饰,因此性能不高,所以使用频率比较低;而ConcurrentHashMap是HashMap在多线程下的替代方案,它在JDK1.7的时候使用的Lock加分段锁的方案来实现线程安全问题的保障的, 而在JDK 1.8的时候使用了大量的CAS、volatile 来实现线程的,并且在JDK 1. 8的时候读取的时候不加锁(读取的数据可能不是最新,因为读取和写入可以同时进行),只有在写的时候才加锁。