前言
本篇文章为记录哔站随狂神学习JAVA所作,若有不谨之处还望斧正。
一、简介
java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks(简称JUC)
JUC 增加了并发编程中很常用的实用工具类,如,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架等。 提供了可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。
二、锁
- Synchronized 锁(内置关键字实现)
- Lock 锁,JUC
- 虚假唤醒问题
- 生产者消费者问题发现发现
- 原因:是 wait() 在阻塞等待时会从阻塞后的代码段执行,而 if 只会判断一次,因此多个线程同时进入就绪队列,如 num == 1 后多个生产者线程开启并进入 wait() ,再次受到唤醒后,会调用多次 num++ 以至于 num 的值域 >{0,1}中。
- 解决方法:将 if 判断换成 while 判断。
- JUC Conditon 实现精准唤醒
- 八锁问题
- 锁本质就是锁Class模板和对象
- Class 模板
- 当资源类中的方法是 static 类型
- OBJ 对象
- 注意: 锁的资源不同,可调用的资源自然也就不一样
三、并发下的不安全集
不安全:并发修改同一资源时IDEA报错
- ArrayList
- HashSet
- 底层实现是 HashMap ,add 方法就是 map.put 了一个 key
- HashMap
解决方法:
- Vector<>
- Collection 工具集的同步方法
- CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
注意:Copy类 优于 Vector 的原因是 Vector 采用Synchronized会使得效率降低
四、线程池 Callable
Callable 其实类似于 Runnable,是接口,是为了创建实例类而设计的,区别:
- 有返回值,通过 future.get() 得到
- 可以抛出异常
- 方法为 call() (函数式接口)
- 有缓存 (两个Callable线程,打印相同结果只会打印一次,提高效率)
- 可能阻塞
关系:
调用方式:通过适配类FutureTask实现
class Call {
Thread(new FutureTask(demo), calls).start(); //Callable不能在Thread直接调用,因此需要借用 Runnable 的实现类 FutureTask 简接调用
}
class demo implements Callable{
public <V> call(){
System.out.println("CALL()");
return <V>;
};
}
五、三大辅助类
- CountDownLatch
- 作用:并发时的计数器(减操作)
...
CountDownLatch cnt = new CountDownLatch(6);
for(int i=0;i<6;i++)
new Thread(()->{ cnt.countDown();}); //递减
cnt.await(); // 计数器为0时唤醒,否则阻塞等待
...
- 使用场景:当需要等待某些线程完成时,如等待所有学生离开后关门。
- CyclicBarrier
- 作用:加法计数器
...
CyclicBarrier cnt = new CyclicBarrier(7, ()->{
System.out.println("召唤龙珠!");//注:此接口为Runnable接口
});
for(int i=0;i<7;i++)
{
final int temp = i;
new Thread(()->{
System.out.println("正在收集第"+temp+"颗龙珠");//注意:此处为new 了个类,因此不能直接用 i,要用final转化(作用域问题,基础还是不牢固)
cnt.await(); //等待, 每执行完一个线程计数器会加一直至7,不至就等待
});
}
...
- Semaphore
- 作用:感觉就是个资源池,类似于OS,以下是抢车位案例的部分代码
Semaphore s = new Semaphore("permits:" 3); //表示共3个资源
for(int i=0;i<6;i++){
new Thread(()->{
try{
s.aquire(); //获得资源,资源上限为3,满了就等到被释放
System.out.println(Thread.currentThread().getName()+"抢到了车位");
TimeUnit.SECONDES.sleep(2);
System.out.println(Thread.currentThread().getName()+"释放了车位");
}catch(IOException e){
System.out.println("ERROR!");
}finally{
s.release();
}
},i);
}
- 使用场景:并发互斥资源的使用、并发限流的最大线程数控制等。
六、读写锁 ReadWriteLock
- 作用:更细粒地描述锁的操作,是独占锁(写锁)与共享锁(读锁)的集合,以下是自定义缓冲区的案例。写锁时只许一个线程操作,读锁时允许多个线程操作。
//自定义缓冲区,手敲的,有错担待
class ReadWriteLockDemo{
myCache cache = new myCache();
for(int i=0;i<5;i++){
final int temp = i;
new Thread(()->{
cache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
for(int i=0;i<5;i++){
final int temp = i;
new Thread(()->{
cache.get(temp+"",);
},String.valueOf(i)).start();
}
}
class myCache{
private volatile Map<String,Object> map = new HashMap<>();
private ReentrantReadWriteLock lock = new ReentranReadWriteLock;
public void put(String key,Object value){
lock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
}catch(...){...}
finally{
lock.writeLock().unlock();
}
}
public void get(String key){
lock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
}catch(...){...}
finally{
lock.readLock().unlock();
}
}
}
七、阻塞队列BlockingQueue
- ArrayBlockingQueue
- 使用场景:线程池、多线程并发处理
- 定义方法
ArrayBlockingQueue blockQueue = new ArrayBlockingQueue<>(capacity); //capacity为队列容量
家族结构:
- 四个 API 方法
- 拓展---同步队列SynchronousQueue
BlockingQueue子类,但和其他的BlockingQueue不同,他不存储元素(capacity==1),put了需要take,否则不能putd