1. 什么是自旋锁?
线程A进入一段程序想要获取自旋锁,但此时自旋锁又被其他线程所拥有,那么线程A会不断循环中自选以检测当前锁是否可用。
例如下面这段程序,线程thread1一直想要获取锁,但此时该锁可能被其他线程所拥有,那么就会不断循环的去获取锁,直至拿到所需的锁;
//创建两个线程,分别对应输出foo和输出bar
Runnable thread1 = () -> {
for (int i = 0; i < n; i++) {
synchronized (lock) {
while (flag){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//run()方法
System.out.print("foo");
flag=true;
lock.notifyAll();//唤醒其他等待的线程
}
}
};
自旋锁的优势:
1、基于自旋锁的机制会让线程循环等待获取锁,使线程始终保持active状态,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
2、非自旋锁的机制,线程在获取不到锁的情况下,会进入阻塞状态,当再一次获取锁的时候,从内核恢复,需要进行线程的上下文切换。
自旋锁会出现的问题:
1、若某个线程持有线程的时间过久,那么其他线程基于自旋锁机制将不断循环等待,极大消耗了cpu。
2、java基于自旋锁是不公平的,无法满足等待时间长的优先获取锁,导致“线程饥饿”的问题。
2. 什么是乐观锁和悲观锁?
悲观锁:JDK1.5之前都是通过synchronized关键词进行线程同步的,这种方式通过一致的锁定协议来协调对共享数据的访问,确认无论是哪一个线程来访问数据,都采用独占锁的方式来访问这些变量。
乐观锁:乐观锁的思想认为数据在一般情况下都不会造成冲突,所以只有当对数据进行更新的时候,才会对数据的是否冲突进行检测,如果发生了冲突,会返回让用户自己去觉得如何去做。
3、乐观锁——CAS机制
(compare and swap)比较并交换,主要应用在CAS有三个操作数,内存值V,旧的预期值A,要修改的新值B,如果内存值和设置的预期值A相同,将内存值V修改为B。
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
应用案例:
例如多个线程操作数据库,需要修改数据库的name,线程进入执行体内,利用CAS技术,首先会去获取name行的值,即内存值V(name=“杜”),然后判断就的预存值A是不是为杜,是不是和V相等,如果相等,那么就允许修改为新值。
优点:
CAS操作确保对内存的读-改-写操作都是原子操作
缺点:
循环时间长、开销大、只能保证一个共享变量的原子操作。
如何改善缺点:
CAS操作单个共享变量的时候可以保证原子的操作,多个变量就不行了,JDK 5之后 AtomicReference可以用来保证对象之间的原子性,就可以把多个对象放入CAS中操作。
总结:
1. 使用CAS在线程冲突严重时,会大幅降低程序性能;CAS只适合于线程冲突较少的情况使用。
1. synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
4. 锁的等级:方法锁、对象锁、类锁?
方法锁和对象锁其本质相同,因此只存在对象锁和类锁的区别?
对象锁:java每个对象都可以做一个实现同步的锁,称为内置锁。线程只有进入同步方法或同步代码块才能获得这个内置锁,然后再退出方法或代码块的时候释放锁,获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。对象锁是用于对象实例方法,或者一个实例对象上的。
类锁:类锁是用于类的calss对象或者类的静态方法中。
★总结:对象锁是用来控制实例方法之间的同步,而类锁是用来控制静态方法(或者静态变量互斥体)之间的同步的。