Java锁的诞生
锁为什么需要,在多线程,高并发下,多个线程同时访问共享资源,都可以对共享资源做修改,很容易出现线程安全问题。
那什么是线程安全问题?
线程安全可以归于这些问题
多个线程同时修改一个变量,原子性、可见性,当然最主要的是操作系统对线程的调度是随机的
原子性是指,线程的一组操作是否是不可分割的,例如售票问题,一个售票机此时只有一张票,
但此时有两个线程,线程1和线程2同时进来看到还剩一张票,同时抢,但由于此时1线程在看到后,转而要去执行线程2,线程2去扣减票数后,又回来继续执行线程1的扣减,这样就会出现超卖
所以,线程不是原子的,就会出现线程安全问题
可见性问题
线程工作是有自己的工作空间的,所以线程之间是不可见的
科普一下
为了解决这样的问题,java有自己的内存模型(JMM)
内存模型的目标就是为了定义程序中变量的访问规则,定义java虚拟机将共享变量存到主内存中,从主内存中读取共享变量。
JMM有如下规定:
所有变量都存储在主内存中,每个线程都有自己的工作内存
工作内存中保存了被该线程使用的变量的副本,线程对变量的操作都应在自己的工作空间中进行,不能直接读写主内存
操作完成后,再通过内存一致性协议刷回主内存
所以,可见性问题就由此诞生,例如线程1对变量正在做修改,此时线程2读取变量,那么就会出现线程安全问题
指令重排
是指在程序运行时为了提高性能,编译器和处理器会对指令进行重排序,重排序
分为编译器优化的重排序、指令级并行的重排序、内存系统的重排序
但是,这些重排序,无论如何变化,都要保证结果和预期结果一致,也就是要遵循as-if-serial语义
而在指令重排情况下就会出现线程安全问题。
最开始java是使用synchronized同步互斥锁,且是重量级锁,效率低下
后来,在jdk1.6中,开发者发现大多数情况下锁并不存在竞争,一把锁往往是由一个线程来获取,所以推出了偏向锁
偏向锁:在每次线程获取锁时,只需要判断当前锁的资源是否是偏向自己的,如果是偏向自己的,就不需要额外的操作
偏向锁的申请流程为:
他会查看在对象头的Mark Word是否标记为偏向锁,如果是偏向锁模式就会继续执行,不是就会升级到判断轻量级锁。
是偏向锁,会查看当前偏向锁记录的线程Id是否和当前操作的线程Id一致,如果一致就可以直接操作
如果不是就会利用cas算法将对象的Mark Word的线程ID修改为当前线程ID,修改成功,那便成功加上了偏向锁,如果修改失败,那就是有多线程争抢资源,升级为轻量级锁
轻量级锁:为了避免直接使用重量级锁,索带来的性能消耗
申请流程:
首先,加锁对象如果没有被锁定,会在线程的栈帧中开辟一个所记录空间,记录对象Mark Word的拷贝,虚拟机使用cas算法将Mark Word 修改为指向锁记录空间的指针,
如果修改成功,则加锁成功,锁的标志位变为00,表示该线程已经加上轻量级锁
如果加锁失败,代表有多个线程在争抢资源,虚拟机会查看对象Mark Word 的指针是否指向当前线程的锁记录空间,如果是,则直接进入同步块执行,
如果不是则加锁失败,锁的标志位变为10,膨胀为重量级锁,同步阻塞执行
那么就会有人想了,既然我在jdk1.6中已经对synchronize关键字,这个同步锁优化升级了,那为什么java的sdk,也就是juc中还提供了Lock锁,这不是闲的吗?
事实上,synchronize仍然有他自己局限性
synchronize他是一个同步阻塞锁,是无法主动释放的,这很容易导致死锁问题,
发生死锁有四个状态(互斥条件、不可释放、请求与保持、循环等待)
然而在大多数情况下,我们希望打破这个“不可释放”,我们希望在申请资源的时候希望,能够在申请不到的时候,主动释放资源,
于是我们又设计了一个锁,我们希望这把锁,可以
具备响应中断、支持超时、非阻塞这样的性质,所以我们,又有了juc包下的锁Lock
Lock锁的更多细节,请看下集