多线程与并发-原理
-- synchronized
线程安全的主要诱因
1.存在共享数据(也称临界资源)
2.存在多条线程共同操作这些共享数据
解决方法:同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后在对共享线程进行操作
互斥锁的特性
1.互斥性:在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性。
2.可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(既在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致。
synchronized锁的不是代码,锁的都是对象
锁的分类
1.获取对象锁
2.获取类锁
获取对象锁的两种方法
1.同步代码块synchronized(this),synchronized(类实例对象),锁是小括号()中的实例对象
2.同步非静态方法(synchronized method),锁是当前实例的对象
获取类锁的两种方法
1.同步代码块(synchronized(类.class)),锁是小括号(),中的类的对象(class对象)
2.同步静态方法(synchronized static method)锁的是当前对象的类对象(class对象)
-- synchronized底层实现原理
实现synchronized的基础
1.java对象头
2.monitor
对象在内存中的布局
1.对象头
2.实例数据
3.对齐填充
对象头结构
Mark Word
monitor(源码地址:https://hg.openjdk.java.net/)
每个对象天生带了一把看不见的锁(monitor锁)
通过C++实现,在hotspot源码objectMonitor.hpp(https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/b44df6c5942c/src/share/vm/runtime/objectMonitor.hpp),
_WaitSetLock:等待池
_EntryList :锁池
用来保存objectWait对象列表,每一个对象锁得线程,都会被封装成objectWait来保存到里面。
_owner是指向持有objectMontior对象,当多个对象同时访问同一段同步代码,首先会进入_EntryList集合里面,当线程获取到对象的montior,就会进入到_object,并把montior的_owner,设置为当前线程,同时montior的计数器_count会加一。若线程调用wait方法,会释放当前持有的montior,_owner恢复成null,_count会减一。同时该线程和objectWait实例就会进入到_waitset集合中等待唤醒。若当前线程执行完毕,_waitset释放montior锁,复位变量的值,以便其他线程获取montior锁
objectMontior结构
同步方法和同步代码区别
1.同步代码块允许重入,即允许多个线程调用同一个方法里的同步代码块
java6以后,synchronized性能得到了很大的提升
1.Adaprive Spinning 自适应自旋
2.Lock Eliminate 锁消除
3.Lock Coarsening 锁粗化
4.Lightwight Locking 轻量级锁
5.Biased Locking 偏向锁
synchronized的四种状态
无锁、偏向锁、轻量级锁、重量级锁
锁膨胀方向:无锁-->偏向锁-->轻量级锁-->重量级锁
偏向锁、轻量级锁、重量级锁的汇总
synchronized和reentrantLock的区别
ReentrantLock(再入锁)
1.位于java.util.concurrent.locks包
2.和countDownLatch、FutureTask、Semaphore一样基于AQS实现
3.能够实现比synchronized更细粒度的控制,如控制fairness
4.调用lock()之后,必须调用unlock()释放锁
5.性能未必比synchronized高,并且也是可以重入的
ReentrantLock公平性设置
1.ReentrantLock failLock = new ReentrantLock(true);
2.参数为true时,倾向于将锁赋予等待时间最久的线程
3.公平锁:获取锁的顺序按先后调用Lock方法的顺序(慎用)
4.非公平锁:抢占的顺序不一致,看运气
5.synchronized是非公平锁
ReentrantLock对比synchronized优点
1.判断是否有线程,或者某个特定线程,在排队等待获取锁
2.带超时获取锁的尝试
3.感知有没有成功获取锁
synchronized和
区别
1.synchronized是关键字,ReentrantLock是类
2.ReentrantLock可以对获取锁的等待时间进行设置,避免死锁
3.ReentrantLock可以去获取各种锁的信息
4.ReentrantLock可以灵活地是先多路通知
5.机制:sync操作的是对象头中的Mark work,ReentrantLock调用的是unsafe的park()方法
-- java内存模型
主内存与工作内存数据存储类型以及操作方式归纳
1.方法里的基本数据类型本地变量将直接存储在工作内存的栈针结构中
2.引用类型的本地变量:引用存储在工作内存中,实例存储在主内存中
3.成员变量、static变量、类信息会被存储在主内存中
4.主内存共享的方式是线程各拷贝一份数据到工作内存,操作完成后刷新回主内存
指令重排序需要满足的条件
1.在单线环境下不能改变程序运行结果
2.存在数据依赖关系的不允许重排序
3.无法通过happens-before原则推导出来的,才能进行重排序
A操作的结果需要对B操作可见,则A与B存在happens-before关系
happens-before的八大原则
1.程序次序规则
2.锁定规则
3.传递规则
4.线程启动规则
5.线程中断规则
7.线程终结规则
8.对象终结规则
volatile和synchronized的区别
-- CAS(compare and swap)
一种高效实现线程安全性的方法
1.支持原子更新操作,适用于计数器,序列发生器等场景
2.属于乐观锁机制,号称lock-free
3.cas操作失败时由开发者决定是否尝试,还是执行别的操作(因此调用失败的线程,不会被堵塞挂起)
CAS思想
1.包含三个操作数据,内存位置(V),预期原值(A)和新值(B)
CAS缺点
-- java线程池
利用Executors创建不同的线程池满足不同场景的需求
Fork/Join框架
把大任务分割成若干个小任务并行执行,最终汇总每个小任务结果后得到大任务结果的框架
为什么要使用线程池
1.降低资源消耗
2.提高线程的可管理性
Executor框架
JUC中的三个Executor接口
1.Executor:运行新任务的简单接口,将任务提交和任务执行细节解耦
2.ExecutorService:具备管理执行器和任务生命周期的方法,提交任务管理器更完善
3.ScheduledExecutorService:支持Futrue和定期任务执行
ThreadPoolExecutor工作流程(最常用线程池)
ThreadPoolExecutor的构造函数
1.corePoolSize:核心线程数量
2.maxmnumPoolSize:线程不够用时能够创建的最大线程数量
3.wordQueue:任务等待队列
4.keepAilveTime:抢占的顺序不一定,看运气
5.threadFactory:创建新线程,Executors.defaultThreadFactory()
handler:线程的饱和策略