目录
1.公平锁和非公平锁
2.可重入锁(递归锁)
3.自旋锁
4.读写锁
1.公平锁和非公平锁
官方的说法:
- 公平锁:是指多个线程按照申请的顺序来获取值
- 非公平锁:是指多个线程获取值的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,可能会造成优先级翻转或者饥饿现象
通俗易懂的说法:
打个比方排队买东西,正常情况就是按照顺序排队买东西,这种情况就是公平锁。但是如果说这个时候有一个有急事他走到前面询问他前面的人能不能先买,如果说可以纳那么这个人就相当于插队,这个时候就是非公平锁。
并发包下的ReentrantLock可以创建boolean类型的公平锁和非公平锁,默认是非公平锁
两者区别
- 公平锁:在并发环境中,每一个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个就占有锁,否者就会加入到等待队列中,以后会按照 FIFO (先进先出)的规则获取锁
- 非公平锁:一上来就尝试占有锁,如果失败在进行排队,优点在于吞吐量比公平锁大,Synchronized是一种非公平锁。
2.可重入锁(递归锁)
官方说法:
- 可重入锁:指的是同一个线程外层函数获得锁之后,内层仍然能获取到该锁,在同一个线程在外层方法获取锁的时候,在进入内层方法或会自动获取该锁
通俗易懂的说法:
家里的大门上了锁,你想回家卧室拿东西,此时你只需要打开家里大门的锁(外层函数),就可以打开卧室的门(内层函数)
注意:ReentrantLock和Synchronized是一种可重入锁,可重入锁最大的作用就是避免死锁。
Synchronized是可重入锁代码示例:
class Phone{
public synchronized void sendSMS() throws Exception{//外层锁
System.out.println(Thread.currentThread().getName()+"t invoked sendSMS()");
sendEmail();//内层锁 当外层锁打开时可访问内层函数
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName()+"t invoked sendEmail()");
}
}
public class Synchronized {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{//线程t1
try {
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t1").start();
new Thread(()->{//线程t2
try {
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t2").start();
}
}
运行结果:
sendSMS()相当于外层函数,sendEmail()相当于内层函数,打开外层锁的同时就可以打开内层锁,然后访问内层函数,而且两个线程地址不同。
ReentrantLock是可重入锁代码示例:
//ReentrantLock 是可重入锁
class Phone implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "tget");
set();
} finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "tset");
} finally {
lock.unlock();
}
}
}
public class ReenterLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
Thread t3 = new Thread(phone);
Thread t4 = new Thread(phone);
t3.start();
t4.start();
}
}
注意:lock()与unlock()要配对,即两个lock就要两个unlock
3.自旋锁
是指定尝试获取锁的线程不会立即堵塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上线文切换的消耗,缺点就是循环会消耗 CPU。
代码如下:
import java.util.concurrent.atomic.AtomicReference;
public class SpainLock {
AtomicReference atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"t come in");
while (!atomicReference.compareAndSet(null,thread)){//CAS自旋锁思想
}
}
public void myUnlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"t unlock");
}
public static void main(String[] args) {
SpainLock spainLock = new SpainLock();
new Thread(()->{
spainLock.myLock();
try {//睡眠5s 在这个过程中t2采取自旋锁 比较交换
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spainLock.myUnlock();
},"t1").start();
try {//保证t1比t2先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spainLock.myLock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spainLock.myUnlock();
},"t2").start();
}
}
t1进入之后t2再进入,t2需要等待t1完成操作时(等待5s)才能执行,在这期间t2执行比较并操作代码也就是while循环里的代码,直到t1unlock,此时t1变为null,t2也跳出循环,最后解锁。
4.读写锁
- 独占锁:指该锁一次只能被一个线程持有
- 共享锁:该锁可以被多个线程持有
对于 ReentrantLock 和 synchronized 都是独占锁;对与 ReentrantReadWriteLock 其读锁是共享锁而写锁是独占锁。读锁的共享可保证并发读是非常高效的,读写、写读和写写的过程是互斥的。
未加读写锁时代码:
import java.util.HashMap;
import java.util.Map;
class MyCache{
private volatile Map map=new HashMap<>();
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"正在写入");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入完成");
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"正在读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成");
}
}
public class ReadWriteLock {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp + "", temp + "");
},String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp + "");
},String.valueOf(i)).start();
}
}
}
运行结果:
从运行结果中可以看出,比如0,在写入到写入完成的过程中,中间加塞了很多操作,也就是没有保证原子性,我们的理想情况是正在写入,然后写入完成,中间不能有任何其他操作。因此使用读写锁ReentrantReadWriteLock实现。
ReentrantReadWriteLock代码示例:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache02{
private volatile Map map=new HashMap<>();
private ReentrantReadWriteLock rwlock=new ReentrantReadWriteLock();//读写锁的使用
public void put(String key,Object value){
rwlock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"正在写入");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入完成");
} finally {
rwlock.writeLock().unlock();
}
}
public void get(String key){
rwlock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"正在读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成");
} finally {
rwlock.readLock().unlock();
}
}
}
public class ReadWriteLock02 {
public static void main(String[] args) {
MyCache02 myCache = new MyCache02();
for (int i = 1; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp + "", temp + "");
},String.valueOf(i)).start();
}
for (int i = 1; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp + "");
},String.valueOf(i)).start();
}
}
}
运行结果:
使用了ReentrantReadWriteLock之后可以看到解决了上述的问题,保证了写入过程中没有任何其他操作,直到完成写的操作。



