JUC:
1、ReentrantLock
a)lock 写在try之前
b)一定要记得在final里面进行unlock()。
2、信号量
(1)acquire()void:尝试获取锁,如果可以正常获取到,则执行后面的业务逻辑,如果获取失败,则阻塞等待。
(2)release()void:释放锁。
/**
* 信号量演示程序
*/
public class ThreadDemo98 {
public static void main(String[] args) {
//创建信号量(做个限流)
Semaphore semaphore = new Semaphore(2,true);
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,
0, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000));
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();
}
}
});
}
}
}
3、计数器 CountDownLauth
计数器是用来保证一组线程同时完成某个操作之后,才能继续后面的任务
(1)CountDownLatch(int):设置一个计数器
(2)await():void:等待,当线程数量不满足CountDownLatch的数量的时候,执行此代码会阻塞等待,直到数量满足之后,再执行await之后的代码
/**
* 计数器示例
*/
public class ThreadDemo99 {
public static void main(String[] args) throws InterruptedException {
//设置一个计数器
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "开始起跑");
try {
Thread.sleep(finalI * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "到达终点");
//将计数器-1
latch.countDown();
}
}).start();
}
//阻塞等待
latch.await();
System.out.println("所有人到达终点了,公布排名");
}
}
问:CountDownLatch 是如何实现的?
答:在CountDownLatch 里面有一个计数器,每次调用countDown()方法的时候计数器-1,直到减为0之后,就可以执行await()之后的代码了
CountDownLatch缺点:
CountDownLatch计时器的使用是一次性的,当用完一次之后,就不能再使用了
4、循环屏障(循环栅栏)
/**
* 循环屏障示例
*/
public class ThreadDemo100 {
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();
}
}
}
(1)cyclicBarrier.await():
1、计数器-1
2、判断计数器是否为0,如果为0执行之后的代码,不为0,阻塞等待。
PS:当计数器为0时,首先会执行await之后的代码,将计数器重置
问:CyclicBarrier 和 CountDownLatch 区别?
答:CountDownLatch 计数器只能使用一次CyclicBarrier 他的计数器可以重复使用。
HashMap
底层实现结构,负载因子,哈希冲突的解决等…线程问题
非线程安全的容器:
(1) JDK1.7死循环
(2)JDK1.8数据覆盖
1、HashMap JDK1.7死循环分析:
- HashMap ->数组+链表/红黑树
- HashMap负载因子(加载因子)0.75倍扩容** :
16 * 0.75 = 进行扩容--------0.75 : 尽量的避免哈希冲突所带来的性能开销问题(空间换时间的方案)。
HashMap在JDK 1.7 的头插法、JDK 1.8 尾插法。
2、HashMap JDK1.8数据覆盖
3、HashMap线程安全方案:
- JDK 1.7是将ConcurrentHashMap分成几个segment进行加锁。悲观锁
- JDK 1.8锁优化,读的时候不加锁,写的时候加锁,使用了大量的CAS、Voiltail …
- Hashtable线程安全的容器:给put方法整体加锁,因此一般情况下不会使用 Hashtable。
4、问:HashMap、Hashtable、ConcurrentHashMap区别?
答:
1.HashMap是非线程安全的容器,它在 JDK 1.7会造成死循环,JDK 1.8会造成数据覆盖;HashtableConcurrentHashMap都是线程安全。
2.Hashtable实现线程安全的手段比较简单,它是在put方法整体加了一把锁,使用synchronized 修饰,因此性能不高,所以使用频率比较低;而ConcurrentHashMap是 HashMap在多线程下的替代方案,它在JDK 1.7的时候使用的 Lock 加分段锁的方案来实现线程安全问题的保障的,而在 JDK 1.8 的时候使用了大量的CAS、volatile来实现线程的,并且在JDK 1.8 的时候读取的时候不加锁(读取的数据可能不是最新,因为读取和写入可以同时进行),只有在写的时候才加锁。