1.Synchronized
是一个关键字,JVM级别的锁。
锁的是对象!!
同时是一个悲观锁、重量级锁。(悲观锁就是老往最坏情况考虑)
隐式加解锁(monitor支持可重入)。
2.使用
1.同步代码块
2.同步方法
区别:锁的粒度不同
【同步代码块】必须传入要锁住的对象 !!!
public void run() {
for(int i=0;i<100;i++){
//线程在外面等
synchronized (this){
//为程序上锁
if(this.ticket >0){
System.out.println(Thread.currentThread().getName()+"还剩下"+ticket--+"张");
}
}
}
}
【同步方法】这个方法同一时间只有一个线程可以进
public synchronized void 方法名(){
方法体;
}
但是如果方法是对象方法的话,不同的对象调用这个方法,是可以同时进的。
为了解决这个问题,1.可以在方法前加static,使其变为类方法。 2.对这个对象控制唯一性(Class对象)
有一个同步队列、一个等待队列。
一般获取锁失败先会CAS自旋一会,如果一直没有释放则是获取失败,加入同步队列,等待获取锁。
wait()后将线程阻塞加入等待队列。
notify()唤醒等待队列中的线程,使其加入同步队列,等待获取锁。
3.实现原理
对象锁monitor,Synchronized锁通过操作monitor指令,获取锁(monitorenter),释放锁(monitorexit),来自动加减锁。
同一时间只有一个线程能获取对象锁。
通常一个获取锁,包含着多个退出指令。这样是避免异常情况下能正常释放锁。
4.CAS
因为它是一个悲观锁,所以在乐观情况下它的效率十分低。
在JDK1.6引入了CAS(乐观锁策略)来优化这个锁。
CAS:不断尝试策略,总是乐观的认为下一次可以获取到锁。
当获取锁失败时候,不是武断地挂起线程,而是再不断地尝试再次获取。
了解CAS要先了解三个变量:V(实际值)O(预期值)N(新值)
if(V==O){
V=N; //成功
}else{
return V; //失败
}
CAS存在3个问题:
1.ABA问题:A先改成1,B在改成0,A再改成1;
解决:加入版本号/名字 1A->0B->1A
2.浪费CPU:因为不断尝试获取锁,会浪费部分CPU完成其操作
解决:自适应自旋
3.不公平:对同步队列中排队等待的线程不公平,因为又可能是自旋的线程先获取到,而等的久的线程却获取不到
解决:Lock公平锁
5.JDK1.6优化
无锁、偏向锁、轻量级锁、重量级锁、锁粗话、锁消除
实现原理:
在Java对象的对象头(32位)中,存放有HashCode、分代年龄、锁标记位
锁标记位:分为无锁(0 01)、偏向锁(1 01)、轻量级锁(00)、重量级锁(10)
锁规定只可升级为更重量的锁,不能降级
偏向锁:这把锁总是一个线程在获取
只会在第一次请求时使用CAS
获取:1.看本线程ID和对象头中的线程ID是否相等 2.相等,直接进入。 3.看标识字段是否为1,为0,就把本线程ID存入,为1就开始竞争。
释放:1.出现竞争,将字段设为001 2.将当前对象锁升级为轻量级锁
轻量级锁:这把锁在某一时刻只有一个线程获取
获取:JVM在线程帧中开辟锁记录空间,并复制锁记录存入。使用CAS将对象头中的锁记录更换成指向线程帧的指针。
释放:CAS将锁记录替换回对象头,失败则升级为重量级锁。
重量级锁:这把锁在同一时刻有多个线程获取
JVM会阻塞重量级锁的进程,等待锁释放会被唤醒
在阻塞之前会自旋,询问是否释放,释放则直接获取
过多的同步锁会造成死锁,(多个线程都在彼此等待着对方的锁释放)所以注意加锁要适量,加锁勿成环。