Synchronized
对于Synchronized的了解
synchronized关键字是为了让多个竞争同一资源的线程不产生线程安全问题,同一时间只能有一个线程可以获得对象锁,从而进入临界区执行代码,其他的线程只能进入等待队列等待拿到锁的线程释放锁后才能进入下一次竞争。
在早期的synchronized中,对象锁属于重量级锁,因为java的线程是映射到操作系统本地的线程,线程同步则依靠的是操作系统底层的Mutex Lock互斥锁,如果需要切换线程,就需要操作系统帮忙,而操作系统帮忙的话就需要从用户态进入到内核态,这里是很耗费时间的。所以我们才称对象锁为重量级锁。
而现在的synchronized对象锁,已经性能优化的很好了,现在很多地方都使用到了synchronized,比如采用了偏向锁,自适应自旋锁,轻量级锁,锁消除,锁粗化等等锁优化手段。
说明
下列给出目录方便复习
使用synchronized的三种方式
静态方法上 普通方法上 代码块中:注意不要使用一个String对象作为锁,因为String是具有缓存功能的
双重检测锁 DCL
public class Singleton {
//禁止指令重排序
private volatile static Singleton lock;
private Singleton() {
}
public static Singleton getInstance() {
//细化锁粒度,增大并发量
if (lock == null) {
//类对象加锁
synchronized (Singleton.class) {
//双重判断
if (lock == null) {
lock = new Singleton();
}
}
}
return lock;
}
}
注意:这里必须使用volatile关键字禁止指令重排。
lock = new Singlet
分为三步:
1.分配空间
2. 初始化
3. 指向分配好的对象
这里有可能进行指令重排,比如顺序为1->3->2,lock就会拿到一个没有初始化好的对象,后续使用就会出问题。所以需要使用volatile关键字禁止指令重排。
构造方法需要使用synchronized修饰吗
并不需要,JVM能帮我们保证构造方法的原子性。
Synchronized原理
代码块上,是由一个monitorenter和monitorexit保护指定的代码临界区。当执行到monitorenter指令的时候,就会尝试获取对象上带的monitor锁(这个锁是使用c++实现的),若锁的计数器为0,则获取成功,否则阻塞等待获得锁的线程进行唤醒。
在方法上,没有monitorenter和moniterexit修饰,而是在方法(包括静态方法)上有标识ACC_SYNCHRONIZED,原理是一样的的。
jdk6之后synchronized关键字做了哪些优化
为了避免直接使用底层monitor锁,做了很多的优化,其中包括:偏向锁,轻量级锁,最后才是使用重量级锁。
首先了解一下JVM实现这些锁,依赖的是每个对象头中存储的MarkWord。
偏向锁:
大部分情况是同一个线程,多次获得锁,并没有竞争的情况,引入了偏向锁。仅使用一次cas操作将对象头中的线程id设为当前线程即可表示获得锁,下次可以直接进入。当发生竞争时,就会涉及到偏向锁的撤销。
- 对象是不可偏向状态
- 不需要撤销
- 对象是可偏向状态
- MarkWord 中指向的线程不存活
- 允许重偏向:退回到可偏向但未偏向的状态
- 不允许重偏向:变为无锁状态
- MarkWord 中的线程存活
- 线程ID指向的线程仍然拥有锁
- 升级为轻量级锁,将 mark word 复制到线程栈中
- 不再拥有锁
- 允许重偏向:退回到可偏向但未偏向的状态
- 不允许重偏向:变为无锁状态
- 线程ID指向的线程仍然拥有锁
- MarkWord 中指向的线程不存活
撤销偏向成功后,会让epoch中数量增加,当达到一个阈值时(默认是40),表示不适合偏向锁,会进行锁升级。
轻量级锁: 适合在代码块比较少(也就是持有锁时间较少),且竞争不是特别激烈的时候,使用轻量级锁(他会自旋一会)。首先会使用cas将对象头中的信息拷贝到线程栈帧里,然后将对象头中的指针设为当前线程,可以支持重入,仅仅在方法中添加一条值为null的锁记录Lock Record即可。每一次进入都会使用cas替换markword中的内容。而偏向锁只会在第一次cas,后续检查即可。
轻量级锁撤销时,也会使用cas将锁记录中的markword替换回对象头中,如果成功表示没有竞争,如果失败了升级为重量级锁。
重量级锁: 使用操作系统mutex互斥锁。每次并不使用轻量级锁那样自旋一会,而是直接阻塞,等待获得锁的线程释放方能唤醒他们继续竞争。这是比较耗费资源的。
synchronized和ReentrantLock的区别
相同点:
- 二者都是可重入锁
- 都是互斥锁
不同点:
- synchronized是基于JVM实现的,而RenentrantLock是基于Java API实现的。
- RenntrantLock有多个选择性通知Condition,而synchronized只有一个。在调用notiifyAll方法时对性能也是一个影响因素。
- RenntrantLock可以使用公平和非公平锁,而synchronized只有非公平锁(可插队,不是按时间顺序)。
- ReentrantLock可以选择取消等待lock.lockInterruptibly(),而synchronized则不行,必须进入。可以通过下列代码测试。
synchronized测试
Thread t2 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("准备搞锁");
synchronized (BlockedForInterruptTest.class) {
try {
System.out.println("睡眠线程获得了锁");
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (BlockedForInterruptTest.class) {
System.out.println("inter拿到了锁");
try {
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t2.getState());
t2.interrupt();
}
}
};
t1.start();
t2.start();
inter拿到了锁
准备搞锁
BLOCKED
睡眠线程获得了锁
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at test.juc.BlockedForInterruptTest$1.run(BlockedForInterruptTest.java:23)
可以看到在阻塞中的线程还是在中断后正常进入了synchronized。
RenntrantLock测试
ReentrantLock lock = new ReentrantLock();
Thread t2 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("准备搞锁");
try {
lock.lockInterruptibly();
for (int i = 0; i < 5; i++) {
System.out.println("睡眠线程获得了锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
};
Thread t1 = new Thread() {
@Override
public void run() {
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("inter拿到了锁");
try {
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t2.getState());
t2.interrupt();
}finally {
lock.unlock();
}
}
};
t1.start();
t2.start();
运行结果
inter拿到了锁
准备搞锁
WAITING
java.lang.InterruptedException
at ...
总结
这篇文章主要对synchronized关键字进行了复习,包括优化以及原理。后续再进行补充。