自拟!并发线程死知识!
自拟!并发线程死知识!
分布式、缓存、线程、数据库都存在涉及再并发访问,操作的问题;此文章则记录了面对各样的场景和问题的解决方法和理论知识。
线程
多个线程共享一个进程的堆和方法区资源,但每个线程又有自己的程序计数器、虚拟机栈、本地方法栈。
所以 堆溢出和 栈溢出 不能说的太绝对,但都是危害性极大的异常。
线程组成
程序计数器: 记录当前线程的运行位置,线程切换后能正常运行。
虚拟机栈: 线程存储变量、常量池、操作数等信息空间
本地方法栈: 为被Native本地方法[底层c语言的方法]服务
堆: 虚拟机中的大头,对象分配内存的空间,所以堆溢出排查,应该首先注意是否对象溢出。
方法区[元空间]: 存储静态信息,已被加载的类、常量、即时编译后的代码等数据
线程状态
↑[阻塞]
0 [初始化创建] 一 1就绪 = 2 运行 一 结束
↓等待[wait]
Synchronized关键字
synchronized属于重量级的锁,效率低下。
但是从Jdk1.6之后,官方对synchronized锁进行了大量优化。比如引入自旋锁、适应性自旋锁、偏向锁等概念。
单例模式
public class SignTest{
private volatile SignTest signtest;
private SignTest(){
}
public static SignTest getSign(){
if(signtest==null){
synchronized(this.class){
if(signtest==null){
signtest = new SignTest();
}
}
}
}
}
volatile:被volatile 修饰的成员会被所有线程可见,静止指令重排
锁升级
无锁状态:
偏向锁:单个程序用,为偏向锁,锁头线程id,每次进入时看当前线程是否指向锁头线程
轻量级锁:通过CAS竞争锁资源,如果竞争失败则升级,标识有线程抢锁,升级为轻量级锁
重量级锁:轻量级锁自旋等待10次,升级为重量级
ReentrantLock 对象
和synchronized经常一起被抬出来,首先reentrantlock是一个对象。
但是和synchronized一样属于可重入锁,用户和synchronized差不多。
提供lock( )和unlock( )等方法控制锁操作
另外,ReentrantLock还实现了一些高级功能:
等待可中断: 提供的lock.lockInterruptibly()方法,可以让没有获得锁的,处于等待状态的线程中断,让其做其他事。
实现了公平锁: 公平锁指的是,锁会让目前处于等待中的线程先占用。非公平锁,则是不管目前线程是什么状态都会去争夺锁资源。synchronized只能是非公平锁,reentrantlock需要通过ReentrantLock(boolean fair)创建时的构造方法指定是否是公平锁默认为非公平false
可选择性通知线程: 使用newCondition()方法达到和notify()与notiflyall()相同效果。
volatile关键字
volatile是修饰变量的关键字,只能保证数据的可见性,用于解决多个线程之间,变量可见性问题
CPU缓存
CPU缓存通常指的就是CPU内存,与CPU主存的关系相当于,redis和数据库的关系。
在数据运算操作中,先是从缓存中取一个值,计算之后,在从内存 > 主存,进行数据保存。
JMM模型
线程和主存进行数据交换的模型
为了防止出现,线程A读到主内存,并且更新;线程B读到本地内存中的旧副本值的场景出现。
则需要使用volatile修饰,标明:当前修饰的值被所有线程可见,并且禁止指令重排
并发编程的三个特性
- 原子性,
- 可见性,当一个线程对变量进行修改时,其他所有线程应该都能看到被修改后的最新值。
- 有序性,代码在编译后的执行顺序应该和编译时相同,
ThreadLocal
threadlocal,相当于在每个线程下创建一个单独的空间。
实现原理就是,记录每一个线程的编号。
线程池!!!
优点
- 复用线程资源,减少创建和销毁的消耗
- 提高响应速度,任务到了直接开始
- 统一管理线程
使用
execute()和submit()执行线程方法:
execute: 不会返回执行结果,无法判断任务成功执行与否
submit(): 返回一个Future类型对象,通过这个对象判断任务执行情况
创建
方法一:构造器
构造器参数:
1:核心线程池大小。
2:最大线程数量
3:空闲时间
4:设置空闲时间的单位
5:工作队列
6:线程工厂
7:拒绝策略
方法二: Executors 方法
- FixedThreadPool ,固定线程数量的线程池,如果没有线程空闲,则任务等待
- SingleThreadExecutor,创建一个固定数量为1的线程,
- CachedThreadPool,创建一个根据实际情况自动扩容的线程,如果没有线程空闲,则新建一个线程加入线程池。
Atomic原子类
原子类的实现逻辑:
通过volatile+native方法,采用CAS思想。
当需要改变原子类的值时,会向将原本的值和期望值进行比较。如果相同则进行更新,而拿到原来的值则是通过Unsafe类的本地方法从内存地址中取出来,并且这个值时通过volatile修饰
线程的并发问题:
当使用到线程时,就不可避免出现资源、锁抢夺问题;
以至于造成,内存泄漏、死锁、数据不一致等问题。
上下文切换导致内存消耗
上下文切换,指线程出现突然结束任务,比如被sleep、wait、阻塞或者时间片用完。CPU会保存当前的线程运行信息,在下一次线程恢复运行时,给予恢复。
当频繁的进行上下文切换时,系统需要消耗大量资源进行保存信息,CPU占用过高,并发异常。
死锁问题
如果死锁以及出现,首先要知道是没有办法依靠系统解决,只能依靠人工的重启、停止服务解决。
所以死锁只能在设计层面打破他出现的四个必要条件任何一个了:
第一,线程的互斥性,由于synchronized 是一种偏向型的排他锁,所以无法控制
第二,线程保持请求,可以一次性请求线程需要的所有锁
第三,不放资源,可以进行判断,当拿不到资源时释放手里的资源。
第四,破坏循环等待
并发容器
ConcurrentHashMap
concurrenthashmap,使用seqment分段锁的形式,让map高效的同时还安全。分段锁即锁住当前操作的结点node,但是不对其他node进行锁操作。
在add()中使用reentantock锁住了方法。
CopyOnWriteArrayList
copyonwrite操作,简单的说就是,当list被修改的时候,copy原来的数据,将被修改的值放入副本中。在将副本的值copy到原先的值中。
ConcurrentLinkedQueue
CAS非阻塞算法实现
BlockingQueue
阻塞队列:
理解为消费者-生成者模型的队列。
ArrayBlockingQueue
读和取的操作都需要拿重入锁,队列容量慢时,放入元素阻塞。为空时,取元素阻塞。
默认情况下为非非公平。