并发容器
在并发下使用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