1 锁
Java中锁的种类繁多,如下表所示。
序号 | 锁 | 描述 | 锁实现 |
---|---|---|---|
1 | 自旋锁 | 当前线程获取锁时,如果该锁被其他线程占用,当前线程循环等待,并不断判断是否成功获取锁,直到获取锁退出循环 | TicketLock,CLHLock,MCSLock |
2 | 阻塞锁 | ||
3 | 可重入锁 | 锁的重用,当前线程使用可重入锁后,在该线程中调用了其他可重入锁的线程,无需重新申请锁,直接在当前锁中执行即可 | synchronized,ReentrantLock |
4 | 读写锁 | 读写分离,资源可以被多个线程同时读,或者被一个线程写,不能多个线程同时读和写 | ReadWriteLock |
5 | 互斥锁 | ||
6 | 悲观锁 | 每次操作前都会加锁,因为,每次操作时都会认为会有其他线程会修改数据 | 表锁,行锁,读锁,写锁 |
7 | 乐观锁 | 每次操作不会加锁,因为,每次操作时都认为其他线程不会修改数据 | |
8 | 公平锁 | 以请求顺序分配锁,等待时间最长的线程最优先获得锁 | |
9 | 可中断锁 | 可中断的锁,线程A执行锁中的任务,另一线程B等待获取该锁,当线程B不想等待时,可以中断等待,执行其他任务 | Lock |
10 | 偏向锁 | 偏向于第一个访问锁的线程,如果该锁没有被其他线程访问,则持有偏向锁的线程永远无需触发同步 |
2 偏向锁
偏向锁即该锁偏向于第一个访问锁的线程,如果该锁没有被其他线程访问,则持有偏向锁的线程永远无需触发同步。消除CAS,降低Cache一致性流量。
大多数情况下锁不仅不存在多线程竞争,而且总由同一个线程多次获得(Hotspot作者研究总结),为了让线程获得锁的代价更低,引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头(Mark Word)和线程栈帧中的所记录中存储锁偏向的线程ID(初始CAS),以后该线程在进入和退出同步块时无需花费CAS加锁和解锁。
- Mark word组成
序号 | 锁状态 | 线程ID | 对象分代年龄 | 偏向锁标志 | 锁标志位 |
---|---|---|---|---|---|
1 | 无锁 | / | 分代年龄 | 0 | 01 |
2 | 偏向锁 | 线程ID | 分代年龄 | 1 | 01 |
3 | 轻量级锁 | 指向栈中锁记录的指针 | / | / | 00 |
4 | 重量级锁 | 指向互斥量(重量级锁)的指针 | / | / | 10 |
5 | GC标记 | / | / | / | 11 |
2.1 偏向锁持有
JVM偏向锁-XX:+UseBiasedLocking默认开启,确认instanace可用,偏向锁可用,即Mark word偏向锁标志位1。
当前线程持有的对象判断对象头的Mark Word中是否存储指向当前线程的偏向锁,如果存在,表示线程获得偏向锁,如果不存在,则测试偏向锁的标识是否设置为1,如果没有设置,使用CAS竞争锁,竞争之后,偏向锁转为轻量级锁,如果设置,尝试使用CAS将对象头的偏向锁指向当前线程。
- 验证对象bias位
如果为0,该对象不可偏向,应该使用轻量级锁算法。 - 验证对象所属InstanceKlass prototype的biase位
确认bias是否被设置,如果没有设置,该类所有对象全部不允许被偏向锁锁定,并且该类所有对象的bias位都需要被重置,使用轻量级锁替换。 - 校验epoch位
校验对象的Mark Word的epoch位是否与该对象所属的InstanceKlass prototype的Mark Word epoch匹配,如果不匹配,表明偏向已经过期,需要重新偏向,偏向线程可以简单使用CAS重新偏向这个锁对象。 - 校验owner线程
比较偏向线程ID与当前线程ID,如果匹配,表明当前线程已经获取偏向,可以安全返回,如果不匹配,对象锁被假定为匿名偏向状态,当前线程应该尝试使用CAS获得偏向,如果失败,尝试撤销,回退到轻量级锁,如果获得偏向成功,直接返回。
2.2 偏向锁撤销
偏向锁使用了一种等到竞争出现才释放锁的机制。当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁撤销需要等待全局安全点,首先暂停拥有偏向锁的线程,检查持有偏向锁的线程是否存活,如果线程非活动状态,对象头设置为无锁状态。如果线程仍然存活,恢复到无锁,最后唤醒暂停的线程。
3 可重入锁
自动获取当前锁。线程级别的锁,当线程A执行的方法M使用synchronized关键字修饰,同时方法M内的方法N也使用synchronized修饰,此时,线程A执行方法N时,无需重新申请锁,直接重用线程A已经申请的锁即可,避免了死锁。
3.1 加synchronized
3.1.1 测试
package lock;
import java.util.logging.Logger;
/**
* Synchronized测试.
*
* @author xindaqi
* @since 2021/4/13 17:01
*/
public class SynchronizedTest implements Runnable {
private static final Logger logger = Logger.getLogger("SynchronizedTest");
@Override
public void run() {
try {
add();
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public synchronized void add() throws InterruptedException {
logger.info("执行add()方法的Thread:" + Thread.currentThread().getName());
logger.info("开始执行add()方法");
Thread.sleep(1000);
logger.info("开始执行delete()方法");
delete();
logger.info("完成add()方法执行");
}
public synchronized void delete() throws InterruptedException {
logger.info("执行delete()方法的Thread:" + Thread.currentThread().getName());
logger.info("开始执行delete()方法");
Thread.sleep(1000);
logger.info("完成delete()方法执行");
}
public static void main(String[] args) {
try {
SynchronizedTest synchronizedTest = new SynchronizedTest();
Thread thread1 = new Thread(synchronizedTest, "thread1");
Thread thread2 = new Thread(synchronizedTest, "thread2");
thread1.start();
thread2.start();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
3.1.2 结果
四月 13, 2021 5:54:27 下午 lock.SynchronizedTest add
信息: 执行add()方法的Thread:thread1
四月 13, 2021 5:54:27 下午 lock.SynchronizedTest add
信息: 开始执行add()方法
四月 13, 2021 5:54:28 下午 lock.SynchronizedTest add
信息: 开始执行delete()方法
四月 13, 2021 5:54:28 下午 lock.SynchronizedTest delete
信息: 执行delete()方法的Thread:thread1
四月 13, 2021 5:54:28 下午 lock.SynchronizedTest delete
信息: 开始执行delete()方法
四月 13, 2021 5:54:29 下午 lock.SynchronizedTest delete
信息: 完成delete()方法执行
四月 13, 2021 5:54:29 下午 lock.SynchronizedTest add
信息: 完成add()方法执行
四月 13, 2021 5:54:29 下午 lock.SynchronizedTest add
信息: 执行add()方法的Thread:thread2
四月 13, 2021 5:54:29 下午 lock.SynchronizedTest add
信息: 开始执行add()方法
四月 13, 2021 5:54:30 下午 lock.SynchronizedTest add
信息: 开始执行delete()方法
四月 13, 2021 5:54:30 下午 lock.SynchronizedTest delete
信息: 执行delete()方法的Thread:thread2
四月 13, 2021 5:54:30 下午 lock.SynchronizedTest delete
信息: 开始执行delete()方法
四月 13, 2021 5:54:31 下午 lock.SynchronizedTest delete
信息: 完成delete()方法执行
四月 13, 2021 5:54:31 下午 lock.SynchronizedTest add
信息: 完成add()方法执行
Process finished with exit code 0
3.1.3 解析
由运行结果可知,方法add和delete多线程调用时,是串行执行,当线程1执行结束后,线程2才启动执行。当add方法中调用delete方法时,仍在当前线程中,没有重新申请锁。
synchronized多线程安全,且是可重入类型的锁。
3.2 未加synchronized
3.2.1 测试
package lock;
import java.util.logging.Logger;
/**
* 非synchronized测试.
*
* @author xindaqi
* @since 2021/4/13 17:49
*/
public class WithoutSynchronizedTest implements Runnable {
private static final Logger logger = Logger.getLogger("SynchronizedTest");
@Override
public void run() {
try {
add();
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void add() throws InterruptedException {
logger.info("执行add()方法的Thread:" + Thread.currentThread().getName());
logger.info("开始执行add()方法");
Thread.sleep(1000);
logger.info("开始执行delete()方法");
delete();
logger.info("完成add()方法执行");
}
public void delete() throws InterruptedException {
logger.info("执行delete()方法的Thread:" + Thread.currentThread().getName());
logger.info("开始执行delete()方法");
Thread.sleep(1000);
logger.info("完成delete()方法执行");
}
public static void main(String[] args) {
try {
WithoutSynchronizedTest withoutSynchronizedTest = new WithoutSynchronizedTest();
Thread thread1 = new Thread(withoutSynchronizedTest, "thread1");
Thread thread2 = new Thread(withoutSynchronizedTest, "thread2");
thread1.start();
thread2.start();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
3.2.2 结果
四月 13, 2021 5:53:05 下午 lock.WithoutSynchronizedTest add
信息: 执行add()方法的Thread:thread2
四月 13, 2021 5:53:05 下午 lock.WithoutSynchronizedTest add
信息: 执行add()方法的Thread:thread1
四月 13, 2021 5:53:05 下午 lock.WithoutSynchronizedTest add
信息: 开始执行add()方法
四月 13, 2021 5:53:05 下午 lock.WithoutSynchronizedTest add
信息: 开始执行add()方法
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest add
信息: 开始执行delete()方法
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest add
信息: 开始执行delete()方法
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest delete
信息: 执行delete()方法的Thread:thread1
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest delete
信息: 执行delete()方法的Thread:thread2
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest delete
信息: 开始执行delete()方法
四月 13, 2021 5:53:06 下午 lock.WithoutSynchronizedTest delete
信息: 开始执行delete()方法
四月 13, 2021 5:53:07 下午 lock.WithoutSynchronizedTest delete
信息: 完成delete()方法执行
四月 13, 2021 5:53:07 下午 lock.WithoutSynchronizedTest add
信息: 完成add()方法执行
四月 13, 2021 5:53:07 下午 lock.WithoutSynchronizedTest delete
信息: 完成delete()方法执行
四月 13, 2021 5:53:07 下午 lock.WithoutSynchronizedTest add
信息: 完成add()方法执行
Process finished with exit code 0
2.2.3 解析
由运行结果可知,方法add和delete多线程调用时,是并行执行,线程1和线程2同时执行。
当不使用锁时,非线程安全。
【参考文献】
[1]https://blog.csdn.net/zengdeqing2012/article/details/103894531
[2]https://www.cnblogs.com/qifengshi/p/6831055.html
[3]https://www.jianshu.com/p/9d3660ad4358?utm_source=oschina-app
[4]https://baijiahao.baidu.com/s?id=1662834023898525632&wfr=spider&for=pc
[5]http://blog.sina.com.cn/s/blog_c038e9930102v2hs.html
[6]https://blog.csdn.net/qq_24434251/article/details/114435631
[7]https://blog.csdn.net/weixin_42213903/article/details/97044043
[8]https://blog.csdn.net/zzti_erlie/article/details/103997713