Java并发编程(JUC)

Java并发编程(JUC)

锁的设计思路

  • 需要用到一个标记来实现互斥,全局变量(值可以是0或1;值也可以是0或大于0的整数,可重入锁的计数)
  • 需要用到一个变量记录当前持有锁的线程
  • 某个线程抢占到了锁,如何处理:
    • 记录当前持有锁的线程
  • 某个线程没有抢到锁,如何处理:
    • 需要阻塞等待(释放CPU资源)。如何实现:
      • wait/notify,缺点是无法指定唤醒某个线程
      • LockSupport.park()/unpark() ,可以阻塞/唤醒指定的线程
      • Condition
    • 需要排队:
      • 将阻塞的线程存储到某个数据结构中排队
  • 锁的释放过程,如何处理:
    • notify/notifyAll,此方法无法指定唤醒某个线程
    • LockSupport.unpark(),唤醒指定的一个线程
  • 抢占锁的公平性(是否允许插队)
    • 公平
    • 非公平(性能较好)

ReentrantLock & AQS

  • AbstractQueuedSynchronizer,简称为AQS,是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很方便的构造出来。
  • 例如,可重入锁ReentrantLock的实现:
    在这里插入图片描述

wait/notify

  • wait和notify/notifyAll一定要配合synchronized使用
  • Java中的“任何一个对象都有一个monitor与之关联”是指每个Java对象都有一个内置的监视器(也称为锁),它可以用来控制对对象的访问,以实现线程同步。
  • 在HotSpot虚拟机中,monitor采用ObjectMonitor实现

Condition

  • Condition condition = lock.newCondition()

  • await 等待

  • signal/signalAll 通知唤醒

  • Condition的优势在于可以为多个线程建立不同的Condition

  • 基于Condition实现生产者消费者模型:

public class ProducerConsumerTest {
    private static final int BUFFER_SIZE = 10;
    private static final Lock lock = new ReentrantLock();
    private static final Condition notFull = lock.newCondition();
    private static final Condition notEmpty = lock.newCondition();
    private static final Product[] buffer = new Product[BUFFER_SIZE];
    private static int tail;
    private static int head;
    private static int count;

    public static void main(String[] args) {
        new Thread(new Producer("P0")).start();
        new Thread(new Consumer("C1")).start();
        new Thread(new Consumer("C2")).start();
    }

    static class Producer implements Runnable {

        private String name;

        public Producer(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                lock.lock();
                try {
                    while (count == BUFFER_SIZE) {
                        System.out.println("buffer is full, " + name + " is waiting");
                        notFull.await();
                    }
                    buffer[tail] = new Product(String.valueOf(i));
                    System.out.println(name + " produce one: " + buffer[tail].getName());
                    tail = (tail + 1) % BUFFER_SIZE;
                    count++;
                    notEmpty.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    static class Consumer implements Runnable {

        private String name;

        public Consumer(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                lock.lock();
                try {
                    while (count == 0) {
                        System.out.println("buffer is empty, " + name + " is waiting");
                        notEmpty.await();
                    }
                    Product tmp = buffer[head];
                    buffer[head] = null;
                    head = (head + 1) % BUFFER_SIZE;
                    count--;
                    notFull.signal();
                    Thread.sleep(100);
                    System.out.println(name + " consume one: " + tmp.getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    static class Product {
        public Product(String name) {
            this.name = name;
        }
        private String name;
        public String getName() {
            return name;
        }
    }
}

阻塞队列

  • ArrayBlockingQueue:基于数组实现的阻塞队列
  • LinkedBlockingQueue:基于链表实现的阻塞队列
  • PriorityBlockingQueue:带优先级的无界阻塞队列,每次出队返回优先级最高的元素
  • DelayQueue:延时阻塞队列
  • SynchronousQueue:一个没有数据缓冲的BlockingQueue
    • Executors.newCachedThreadPool(),使用的就是SynchronousQueue

CountDownLatch

  • CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,让一组线程等待另一组线程执行完后再执行。
  • 共享锁的实现,使用计数器
  • 处理流程:
    • 假设有2组线程,需要实现让A组的线程等待B组的线程
    • 设置计数器初始值,通常为B组的线程数量
    • 让A组的线程等待计数器归零(调用await()方法)
    • 当B组中的一个线程完成自己任务后,触发计数器的值减1(调用countDown()方法)
    • 当计数器的值为0时,唤醒所有被阻塞的A组的线程

CyclicBarrier

  • 循环屏障,可循环使用(Cyclic)的屏障(Barrier),或者叫栅栏
  • 让一组线程到达一个同步点时被阻塞,直到最后一个线程到达同步点的时候,屏障会放开,所有被阻塞的线程恢复执行后续逻辑
  • 处理流程:
    • 设置计数器初始值,通常为线程数量
    • 当一个线程到达同步点时(调用await()方法),阻塞线程数+1
    • 当阻塞线程数达到计数器的设定值时,唤醒所有被阻塞的线程
    • 计数可以重置后循环使用

Semaphore

  • 信号量,可实现限流器,限制资源的访问
  • Semaphore(int permits)/Semaphore(int permits, boolean fair) 初始化时指定许可个数
  • acquire()/acquire(int permits) 获取1个/多个许可
  • release()/release(int permits) 释放1个/多个许可
  • 公平/非公平(是否允许插队)

ThreadLocal

线程间变量隔离,每个线程有各自独立的变量副本

在这里插入图片描述
ThreadLocal的内存泄漏问题:

  • Entry中的key为弱引用,value是强引用
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
  • 如果Thread没有被回收,则ThreadLocalMap不会被回收,则其中的Entry中的key不一定会被回收(key是弱引用,当没有其他强引用时会被回收),value不会被回收。

  • 一般使用线程会使用池化技术,一个线程使用完后不会被销毁,而是会复用,所以会存在ThreadLocal的内存泄漏问题。(例如:tomcat的处理请求会从线程池中取出线程执行当前的请求)

  • 如何解决内存泄漏问题:调用ThreadLocal的remove方法

  • 实际解决内存泄漏的案例:

    • tomcat请求线程:可以在请求返回的filter中调用ThreadLocal的remove方法
    • com.github.pagehelper分页插件:基于ThreadLocal实现分页参数的传递,分页查询前调用startPage,使用threadLocal存储分页参数,PageInterceptor实现了Mybatis的拦截器接口执行分页查询逻辑,在PageInterceptor的拦截方法中的finally语句块中调用ThreadLocal的remove方法

Fork/Join

  • 任务的拆分计算与结果合并

在这里插入图片描述

  • ForkJoinTask

    • RecursiveAction
    • RecursiveTask
    • CountedCompleter
    • 等等
  • fork-让task异步执行

  • join-让task同步执行,等待获取返回值

  • ForkJoinPool,专门运行ForkJoinTask,线程复用

双端队列

  • 工作窃取算法
    • 双端队列,可以从头和尾来获取任务
    • 每个线程有各自的队列,一个线程在执行完毕自己队列中的任务之后,可以窃取其他线程队列中的任务来执行。
    • 为了减少竞争,原本的线程从队列头部获取任务,窃取任务的线程从队列尾部获取任务。

ConcurrentHashMap

ConcurrentHashMap中的设计思想:

  • 计算元素存放到数组下标:(n - 1) & hash
  • put方法,当hashKey对应数组下标不存在node时,CAS操作在当前数组下标添加一个node
  • put方法,当hashKey冲突,node存在时,synchronized(node),然后修改该node(链表或红黑树)上的节点的值。(锁的粒度仅为该node,这是jdk1.8之后的优化,jdk1.7锁的是segment)
  • 扩容
    • 支持多个线程协助并发扩容,每个线程负责数组的一段区间。
    • 高低位迁移,需要迁移的数据放在高位链表,不需要迁移的放在低位链表,低位链表在扩容之后的索引与原哈希表中的索引位置一样,高位链表的索引位置是原索引位置+n。
  • 元素个数的统计
    • 在多线程情况下,如果每个线程都自旋CAS修改size的值,会有性能问题,所以没有用此方案。
    • 线程竞争不激烈时,直接CAS(baseConunt)
    • 线程竞争激烈时(CounterCell[]数组已初始化或CAS(baseConunt)失败),使用数组分片统计
    • 数组的方式,分片计数。CounterCell[]数组长度初始为2,线程随机算法选择数组元素执行CAS修改value
      //Contended注解,对齐填充到64字节,解决CPU缓存的伪共享问题
      @sun.misc.Contended static final class CounterCell {
          volatile long value;
          CounterCell(long x) { value = x; }
      }
    
    • 汇总数组的值+baseCount的值来完成数据累加。
  • 当链表长度大于8,并数组长度大于等于64时,链表转换为红黑树。

红黑树

  • 二叉搜索树
    • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
    • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。
    • 它的左右子树也分别为二叉搜索树。
    • 查询时间复杂度介于O(logN)和O(N)之间
  • 平衡二叉树(AVL树):严格平衡,左子树和右子树的高度差的绝对值不超过1
  • 红黑树:一种接近平衡的二叉搜索树。
    • 红黑树确保没有一条路径会比其他路径长两倍,因而是接近平衡的。
    • 红黑树是接近平衡的,是因为它并没有像AVL树的平衡因子的概念,它只是靠着满足红黑节点的5条性质来维持一种接近平衡的结构,并没有严格的卡定某个平衡因子来维持绝对平衡,所以红黑树的整体性能比AVL树更好。

在这里插入图片描述

FutureTask

//futureTask.get()会阻塞当前线程
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            Thread.sleep(5000);
            return "Hello World!";
        });
        futureTask.run();
        System.out.println(futureTask.get());

CompletableFuture

//非阻塞的方式异步获取返回值
        CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "Hello World!";
        }).thenApply((x) -> x + " Now..").thenAccept(x -> System.out.println(x));
//处理异常情况1
        CompletableFuture.supplyAsync(() -> {
            int num = Integer.parseInt("abc");
            return num;
        }).exceptionally(th -> {
            System.out.println("方法执行出错:" + th.getMessage());
            return 0;
        }).thenAccept((x) -> System.out.println(x + " Now.."));
//处理异常情况2
        CompletableFuture.supplyAsync(() -> {
            int num = Integer.parseInt("abc");
            return num;
        }).handle((x, th) -> {
            if (th != null) {
                System.out.println("方法执行出错:" + th.getMessage());
                return 0;
            }
            return x;
        }).thenAccept((x) -> System.out.println(x + " Now.."));
//组合处理多个异步任务
        CompletableFuture.supplyAsync(() -> {
            int num = Integer.parseInt("100");
            return num;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            int num = Integer.parseInt("200");
            return num;
        }), (r1, r2) -> r1 + r2).thenAccept(x -> System.out.println(x));
  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值