JUC并发编程(一)

87 阅读3分钟

前言

本篇文章为记录哔站随狂神学习JAVA所作,若有不谨之处还望斧正。

一、简介

java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks(简称JUC)

JUC 增加了并发编程中很常用的实用工具类,如,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架等。 提供了可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。

二、锁

  1. Synchronized 锁(内置关键字实现)
  2. Lock 锁,JUC
  3. 虚假唤醒问题
  • 生产者消费者问题发现发现
  • 原因:是 wait() 在阻塞等待时会从阻塞后的代码段执行,而 if 只会判断一次,因此多个线程同时进入就绪队列,如 num == 1 后多个生产者线程开启并进入 wait() ,再次受到唤醒后,会调用多次 num++ 以至于 num 的值域 >{0,1}中。
  • 解决方法:将 if 判断换成 while 判断。
  1. JUC Conditon 实现精准唤醒
  2. 八锁问题
  • 锁本质就是锁Class模板和对象
  • Class 模板
    • 当资源类中的方法是 static 类型
  • OBJ 对象
  • 注意: 锁的资源不同,可调用的资源自然也就不一样

三、并发下的不安全集

不安全:并发修改同一资源时IDEA报错

  1. ArrayList
  2. HashSet
  • 底层实现是 HashMap ,add 方法就是 map.put 了一个 key
  1. HashMap

解决方法:

  1. Vector<>
  2. Collection 工具集的同步方法
  3. CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap

注意:Copy类 优于 Vector 的原因是 Vector 采用Synchronized会使得效率降低

四、线程池 Callable

Callable 其实类似于 Runnable,是接口,是为了创建实例类而设计的,区别:

  • 有返回值,通过 future.get() 得到
  • 可以抛出异常
  • 方法为 call() (函数式接口)
  • 有缓存 (两个Callable线程,打印相同结果只会打印一次,提高效率)
  • 可能阻塞

关系: image.png

调用方式:通过适配类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>;
    };
}

五、三大辅助类

  1. CountDownLatch
  • 作用:并发时的计数器(减操作)
    ...
    CountDownLatch cnt = new CountDownLatch(6);
    for(int i=0;i<6;i++)
            new Thread(()->{ cnt.countDown();}); //递减
    cnt.await(); // 计数器为0时唤醒,否则阻塞等待
    ...
  • 使用场景:当需要等待某些线程完成时,如等待所有学生离开后关门。
  1. 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,不至就等待
        });
    }
    ...
  1. 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

  1. ArrayBlockingQueue
  • 使用场景:线程池、多线程并发处理
  • 定义方法
ArrayBlockingQueue blockQueue = new ArrayBlockingQueue<>(capacity); //capacity为队列容量

家族结构: image.png

  1. 四个 API 方法

image.png

  1. 拓展---同步队列SynchronousQueue

BlockingQueue子类,但和其他的BlockingQueue不同,他不存储元素(capacity==1),put了需要take,否则不能putd