Day118 并发编程进阶

并发容器

在并发下使用ArrayList是不安全的,会报 ConcurrentModificationException
解决方法:Vector(不推荐),工具类Collections.synchronizedList(),JUC下的CopyOnWritearraylist

  • CopyOnWriteArrayList
    • COW思想:写入时复制,为了防止写入数据时多线程操作同一个数组,会导致数据覆盖等不安全问题,可以写入时复制一个数组,写入以后再插入进原数组。
    • 比起 Vector ,这种实现效率更高(用 Lock 锁带起 Synchronized)
  • CopyOnWriteArraySet
  • ConcurrentHashMap
  • ThreadLocal
  • BlockingQueue:阻塞队列,继承于Queue类,有4组API和众多的实现类。
    • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
    • LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
    • SynchronousQueue:没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素
      在这里插入图片描述
  • ThreadLocal:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
  • 总结:线程并发,传递数据,线程隔离
  • 原理:Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap的key是LocalThread对象本身,value则是要存储的对象。简单说 ThreadLocal 就是一种以空间换时间的做法,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
  • 应用:最典型的是管理数据库的Connection,这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
  • 内存泄漏:但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。想要避免内存泄露就要手动remove()掉。
    在这里插入图片描述
    https://www.bilibili.com/video/BV1N741127FH

并发辅助类

  • CountDownLatch:CountDownLatch是Java并发包下的一个工具类,latch是门闩的意思,顾名思义,CountDownLatch就是有一个门闩挡住了里面的人(线程)出来,当count减到0的时候,门闩就打开了,人(线程)就可以出来了。
  • 相当于一个减法计数器,制定数量的线程全部完成后再开始下面的操作。
    countDownLatch.countDown(); // 数量-1
    countDownLatch.await(); // 等待计数器归零,然后再向下执行
    每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续执行。
  • 原理:CountDownLatch类中有一个静态内部类 Sync ,它继承自 AbstractQueuedSynchronizer ,所以可以看出,CountDownLatch的功能还是通过AQS来实现的。使用CountDownLatch就需要指定count值,且必须大于0,而这个count值最终是赋值给了AQS的state。
    https://baijiahao.baidu.com/s?id=1663210842526248944&wfr=spider&for=pc
// 计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 总数是6,必须要执行任务的时候,再使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" Go out");
                countDownLatch.countDown(); // 数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); // 等待计数器归零,然后再向下执行

        System.out.println("Close Door");

    }
}
  • CountDownLatch有几个问题:首先CountDownLatch在await之后必须依靠别的线程来给它countDown,打开门闩;其次CountDownLatch在countDown到0之后,该CountDownLatch的生命周期就结束了,它不能重用。
  • Cyclebarrier :加法计数器。CyclicBarrier,译作回环栅栏。它的使用方法和CountDownLatch差不多,也有一个计数值,叫做parties。
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 集齐7颗龙珠召唤神龙
         */
        // 召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });

        for (int i = 1; i <=7 ; i++) {
            final int temp = i;
            // lambda能操作到 i 吗
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
  • Semaphore: Semaphore用来控制同时操作某个资源的线程数量。多个共享资源互斥的使用,并发限流,控制最大的线程数。
    semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止。
    semaphore.release(); 释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
package com.kuang.add;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量:停车位! 限流!
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                // acquire() 得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // release() 释放
                }

            },String.valueOf(i)).start();
        }

    }
}

原子类

线程不安全的例子

public class AtomicMain {

    public static void main(String[] args) throws InterruptedException {

        ExecutorService service = Executors.newCachedThreadPool();

        Count count = new Count();
        // 100个线程对共享变量进行加1
        for (int i = 0; i < 100; i++) {
            service.execute(() -> count.increase());
        }

        // 等待上述的线程执行完
        service.shutdown();
        service.awaitTermination(1, TimeUnit.DAYS);


        System.out.println("公众号:Java3y---------");
        System.out.println(count.getCount());
    }

}

class Count{

    // 共享变量
    private Integer count = 0;
    public Integer getCount() {
        return count;
    }
    public  void increase() {
        count++;
    }
}

问题所在:count++并不是原子操作。因为count++需要经过读取-修改-写入三个步骤。但只做一个++这么简单的操作,如果用synchronized锁,未免有点小题大做了,这时候需要用到原子变量。


class Count{

    // 共享变量(使用AtomicInteger来替代Synchronized锁)
    private AtomicInteger count = new AtomicInteger(0);
    
    public Integer getCount() {
        return count.get();
    }
    public void increase() {
        count.incrementAndGet();
    }
}


// Main方法还是如上

原子变量类在java.util.concurrent.atomic包下,总体来看有这么多个:
在这里插入图片描述
实现原理:Atomic包里的类基本都是使用Unsafe实现的包装类。而Unsafe底层实际上是调用C代码,C代码调用汇编,最后生成出一条CPU指令cmpxchg,完成操作。这也就为啥CAS是原子性的,因为它是一条CPU指令,不会被打断。

AtomicInteger:AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。

Atomic系列的类中的核心方法都会调用unsafe类中的几个本地方法。我们需要先知道一个东西就是Unsafe类,全名为:sun.misc.Unsafe,这个类包含了大量的对C代码的操作,包括很多直接内存分配以及原子操作的调用,而它之所以标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患,需要小心使用,否则会导致严重的后果,例如在通过unsafe分配内存的时候,如果自己指定某些区域可能会导致一些类似C++一样的指针越界到其他进程的问题。

https://www.jianshu.com/p/5c9606ee8e01

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值