一.锁的理解
锁这个东西可以理解为对一份资源的一份合同,当你需要使用这个资源时,你需要签订这份合同,而根据占有线程的要求或其他要求,可能导致合同的内容存在不同的差异,因此又衍生出了不同种类的锁.
二.锁的种类
1.自旋锁
当一个线程在获取锁的时候,如果锁已经被其他线程过去,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环.例如cas操作中通过自旋修改i的值.
2.乐观锁
假定没有冲突,在修改数据时,如果发现数据和之前获取的不一致,则读取最新的数据,修改后重试修改.
3.悲观锁
假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始加锁.
#ps:乐观锁和悲观锁的区别在于,乐观锁只在
写数据时还会,对该数据进行比对,如果不同则获取,而悲观锁从读数据就开始加锁,性能不如乐观锁,常见的乐观锁如cas操作
4.独享锁(写锁)
独享锁就是我们常见的锁,给线程加上锁之后,只有该线程可以对数据进行访问,其他线程不可以再访问该数据.
5.共享锁(读锁)
共享锁是一种可以允许多个线程同时去读,但是不允许其他线程进行修改,及对线程加读锁后不可以再想该线程添加写锁.
6.可重入锁
一个线程加锁后,该线程还可以继续向该资源进行加锁的操作.例如Synchronized,ReentrantLock.
7.不可重入锁
一个线程加锁后,该线程也无法继续给自己加锁.
8.公平锁和非公平锁
是否按照先来先得的方式获得
三.Synchronized关键字使用
1.用在普通方法上的对象锁
该用法把Synchronized放在普通的方法上,然后在使用使,会禁止同一个对象再次访问该方法.而对于该类下的其他对象访问该方法时不会进行限制.
2.用在静态方法上的类锁
该方法是把Synchronized放在静态方法上,这样在同一时刻,该类下的所有对象只有一个可以执行该方法,因此也称为类锁.
源码如下:
public class SynchronizedTest {
synchronized public void test1(){
System.out.println(Thread.currentThread().getName()+"业务逻辑执行中");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public static void test2(){
System.out.println(Thread.currentThread().getName()+"业务逻辑执行中");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SynchronizedTest test=new SynchronizedTest();
Thread thread=new Thread(()->{
SynchronizedTest test1=new SychronizedTest();
test1.test2();
});
// SynchronizedTest test=new SynchronizedTest();
thread.start();
test.test2();
}
}
3.synchronized代码块语法糖
synchronized(this){
方法
};这样相当于对于普工方法增加sychrongized是一个对象锁.
synchronized(类.class){
};相当于静态方法增加sychronized,是一个类锁.
*ps:类对象不会再两个jvm中产生限制
synchronized属于可重入,独享,悲观锁
四.synchronized的底层原理
1.对象在内存中存储
对象的实例字段及其对应的值存在堆中,方法存储在方法区中. 静态字段不会存在堆里.
这里重点是在堆里存储了一个对象头,对象头中存在一个引用,该引用指向了对应的类.
对象头中的内容包括:
Mark Word
Class Meta address
Arraylength
synchronized锁的标记状态及存储在MarkWord中
2.Mark Word详解
Bitfields | Tag | State | |||
---|---|---|---|---|---|
Hashcode | Age | 01 | Unlocked | (未锁定) | |
ThreadId | Age | 1 | 01 | Biased/biasable | (偏向锁) |
Lock record address | 00 | Light-weight locked | (轻量级锁) | ||
Monitor address | 10 | Heavy-weight locked | (重量级锁) | ||
Forwarding addresss,etc | 11 | Marked for GC |
(markword长度由造作系统决定,如果操作系统为32位则长度为32.如果操作系统为64位则长度位64.)
1.初次抢锁:当还为锁定时,MarkWord的内容为表中第一行中的内容,如果有线程来进行加锁,该线程会把第一行内容拷贝到栈帧中,栈帧名字为LockRecord,然后进行cas操作,对MarkWord进行修改,修改为第三行中的内容.如果修改成功,则成功加锁.否则则失败进行自旋,当自旋到一定的次数后进行升级.(如果cas中没有复制到为初次的状态,则直接升级)
2.抢锁失败的线程会进行自旋,当自旋到一定程度,会进行锁升级,将轻量级锁中的内容进行修改,修改为第四行中的锁.(每个对象都可能存在一个对象监视器,对象监视器会把这个锁进行升级).Mointor中存在着owner和entryList其中owner代表当前持有锁的线程.entryList存储为没抢到锁的线程.并把该线程挂起进入阻塞状态.
3.偏向锁:这个锁不会对锁进行释放.当其他线程来抢占时,会进行锁升级.然后回退会关闭偏向锁.但是锁不会修改为未锁定的状态(jdk1.6后平时在使用时偏向锁会自动开启.通过jvm参数-XX:–UseBissedLocking来禁用偏向锁)
3.synchronized和wait/notify
使用wait/notify后可以将锁进行释放,使线程进入到waiting状态.当其他线程进行唤醒时,线程会进入到阻塞状态,等待获取资源.
总结:
锁这个东西本质就是一份合同,配合着不同的场景会产生不同的效果.同时一个锁可能同时属于多种锁.但是悲观锁和乐观锁不共存,可重入和不可重入不可共存,独享和共享锁不可共存.由于所学有限,只能写这样了.忘批评指正