JUC学习笔记
虚假唤醒
当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了
,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁
解决:使用while替换if,即使唤醒判断之后,下次被唤醒仍会进行条件判断
CopyOnWrite
写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种通用优化策略。其核心思想是,如果有多个调用者(Callers)同时访问相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
通俗易懂的讲,写入时复制技术就是不同进程在访问同一资源的时候,只有更新操作,才会去复制一份新的数据并更新替换,否则都是访问同一个资源。
常用的辅助类
CountDownLatch:倒计数器
CyclicBarrier:正计数器
Semaphore:(PV)信号量,acquire和release管理
ReadWriteLock:读写锁,写锁只能被一个线程占有,读锁可以被多个线程占有,读写不能共存
线程池
【强制】线程池不允许使用 Executors去创建,而是通过 ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool和ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
Executors三大方法:
1 | Executors.newSingleThreadExecutor();// 单个线程 |
Executors的方法本质上是对ThreadPoolExecutor的封装,源码如:
1 | public static ExecutorService newSingleThreadExecutor() { |
ThreadPoolExecutor七大参数:
1 | public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小 |
拒绝策略:
1 | new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常 |
函数式接口
- 函数式接口: 只有一个方法的接口
Function<String,String> function = (输入值)->{return 输出值;};
- 断定型接口:有一个输入参数,返回值只能是 布尔值
Predicate<String> predicate = (str)->{return str.isEmpty(); };
- Consumer 消费型:只有输入,没有返回
Consumer<String> consumer = (str)->{System.out.println(str);};
- Supplier 供给型:没有输入,只有返回值
Supplier supplier = ()->{ return 1024; };
完整代码参考(没用lambda):
1 | public class Demo04 { |
Stream流计算
1 | /** |
异步回调
1 | //异步的异常捕获并作出相应的处理 |
JMM
区分:java内存结构(堆栈等)
JMM就是Java内存模型(java memory model),java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。Java内存模型规定所有的变量都存储在主内存中,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量
Volatile 是 Java 虚拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁是同一把锁
为何i++无法保证原子性?先读取i的值,再+1,最后赋值到i,三步操作。i=j同理。
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量
才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便
随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机
遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变
量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中, 以便后续的write使用write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
指令重排(有序性?)
什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码—>编译器优化的重排—> 指令并行也可能会重排—> 内存系统也会重排—-> 执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
volatile:保证有序性->内存屏障->避免指令重排
单例模式
线程安全版,防反射锁类构造器,防并发锁实例构造器
1 | public class LazyMan { |
CAS
比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环(底层是自旋锁)
缺点:
1、 循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题 (改了又改回去,用版本号(时间戳)解决)
1 |
|
出现死锁怎么办?
打开idea终端:
- 使用 jps -l 定位进程号
- 使用 jstack 进程号 找到死锁问题