一、什么是锁?
将某种资源私有化的一种物品,没错java里面的锁也是这种特性,它可以让某个方法,某个变量或某个通道,在某个时刻下只能被一个线程占用。只有当这个锁释放了,另外的线程才可以使用。
二、锁得种类有哪些?
1)乐观锁与悲观锁
乐观锁(概念):乐观锁认为一个线程去拿数据的时候不会有其他线程对数据进行更改,所以不会上锁。
实现方式:CAS机制+版本号机制(如果只使用CAS会产生ABA问题)
悲观锁(概念):悲观锁认为一个线程去拿数据时一定会有其他线程对数据进行更改。所以一个线程在拿数据的时候都会顺便加锁,这样别的线程此时想拿这个数据就会阻塞。比如Java里面的synchronized关键字的实现就是悲观锁。实现方式:就是加锁。
2)可重入锁
定义:对于同一个线程在外层方法获取锁的时候,在进入内层方法时也会自动获取锁。
优点:避免死锁
举例:ReentrantLock、synchronized
3)自旋锁与自适应自旋锁
自旋锁:
背景:线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒降低性能。同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。故有自旋锁。
核心思想:让该线程等待一段时间【执行一段无意义的循环(自旋))】,不会被立即挂起,看持有锁的线程是否会很快释放锁。在JDK1.6中自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整。
适应自旋锁:
背景:自旋虽然可以避免线程切换带来的开销,但占用了处理器的时间。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,浪费性能上。所以说,自旋等待的时间(自旋的次数)必须要有一个限度,如果自旋超过了定义的时间仍然没有获取到锁,则应该被挂起。 如果手动调整自旋次数,因为无法得知一个合适的数字,会带来诸多不便。如设置为15次,结果17次时才释放锁,这种情况下本该继续等待两次,但是却被挂起,导致性能不到最优。于是JDK1.6引入自适应的自旋锁。
核心思想:若自旋成功,则下次自旋的次数会更多(上次成功,此次很可能也成功)。反之,若某个锁很少自旋成功,则以后获取锁时减少自旋次数甚至省略掉自旋过程,以免浪费处理器资源。
三、锁升级
锁的状态: 无锁---->偏向锁----->轻量级锁(通过自适应自旋锁实现)---->重量级锁
这种锁升级只针对synchronized。
4)偏向锁
顾名思义,偏向某一个线程。JVM使用CAS操作把线程ID记录到对象的Mark Word当中,并修改标识位,name当前线程就拥有了这把锁。程序运行几秒后默认开启,对象进行过hashcode操作后无法进入偏向锁状态。
偏向级锁不需要操作系统的介入,JVM使用CAS操作将线程ID放入对象的Mark Word字段中,于是线程获得了锁,可以执行synchronized代码块的内容,当线程再次执行到这个synchronized的时候,JVM通过锁对象的Mark Word判断 :当前线程ID还存在,还持有这个对象的锁,于是就可以继续进入临界区执行,而不需要再次获得锁
偏向锁可以进行重偏向操作,即有30个对象(对象都是同一个类的对象)偏向t1线程时,有个t2线程撤销偏向t1的对象(即让t2线程获取对象锁),当撤销偏向的对象数量超过20个,后10个对象会重偏向t2线程当撤销次数超过40次时对象会变为不可偏向状态,撤销次数太多说明这个类的竞争很激烈不适用偏向锁
总结一点:偏向级锁就是为了消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。
5)轻量级锁
轻量级锁是由偏向级锁升级来的,当一个线程运行同步代码块时,另一个线程也加入想要运行这个同步代码块时,偏向锁就会升级为轻量级锁。
首先,JVM会将锁对象的Mark Word恢复成为无锁状态,在当前两线程的栈桢中各自分配一个空间,叫做Lock Record,把锁对象account的Mark Word在两线程的栈桢中各自复制了一份,官方称为:Displaced Mark Word
然后一个线程尝试使用CAS将对象头中的Mak Word替换为指向锁记录的指针,如果替换成功,则当前线程获得锁,如果失败,则当前线程自旋重新尝试获取锁。当自旋获取锁仍然失败时,表示存在其他线程竞争锁(两个或两个以上线程竞争同一个锁),则轻量级锁会膨胀成重量级锁
6)重量级锁
我们上面提到,当多个线程竞争同一个锁时,会导致除锁的拥有者外,其余线程都会自旋,这将导致自旋次数过多,cpu效率下降,所以会将锁升级为重量级锁。
重量级锁需要操作系统的介入,依赖操作系统底层的Muptex Lock。JVM会创建一个monitor对象,把这个对象的地址更新到Mark Word中。
当一个线程获取了该锁后,其余线程想要获取锁,必须等到这个线程释放锁后才可能获取到,没有获取到锁的线程,就进入了阻塞状态。
参考资料:(1条消息) Java锁之偏向级锁、轻量级锁、重量级锁_什么是偏量级锁_Colourful.的博客-优快云博客
四、死锁是什么?什么情况下会发生死锁现象?
死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的互相阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
/**
* 当t1线程需要依次锁demo1, demo2 对象时,t2线程需要依次锁demo2, demo1 对象时有可能会出现t1把demo1锁了 t2把demo2锁了
* 导致t1 t2线程无法继续执行从而达到死锁的效果
*/
public class DeadLock {
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
Demo2 demo2 = new Demo2();
Thread thread1 = new mythread(demo1, demo2);
Thread thread2 = new mythread2(demo1, demo2);
thread1.setName("t1");
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
class mythread extends Thread {
Demo1 demo1;
Demo2 demo2;
public mythread(Demo1 demo1, Demo2 demo2) {
this.demo1 = demo1;
this.demo2 = demo2;
}
@Override
public void run() {
synchronized (demo1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (demo2) {
}
}
}
}
class mythread2 extends Thread {
Demo1 demo1;
Demo2 demo2;
public mythread2(Demo1 demo1, Demo2 demo2) {
this.demo1 = demo1;
this.demo2 = demo2;
}
@Override
public void run() {
synchronized (demo2) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (demo1) {
}
}
}
}
class Demo1 {
public synchronized void doSome(Demo2 demo2) {
System.out.println(Thread.currentThread().getName() + "-----" + "Demo1 doSome Begin");
System.out.println(Thread.currentThread().getName() + "-----" + "Demo1 doSome Over");
}
}
class Demo2 {
public synchronized void doSome(Demo1 demo1) {
System.out.println(Thread.currentThread().getName() + "-----" + "Demo2 doSome Begin");
System.out.println(Thread.currentThread().getName() + "-----" + "Demo2 doSome Over");
}
}