自旋锁和互斥锁实例_JUC多线程与高并发面试题——公平锁/非公平锁/重入锁/递归锁/自旋锁...

一、公平锁和非公平锁

1.1 公平锁和非公平锁分别是什么

公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。

非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

1.2 区别

并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁。

关于两者的区别:

公平锁

就是很公平,在并发环境中,每个线程在获取锁时,会先查看此锁维护的等待队列。如果为空或者当前线程是等待队列的第一个,就占有锁,否则就加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁

非公平锁比较粗鲁,上来就尝试占有锁,如果尝试失败,就再采取类似公平锁的那种方式。

非公平锁的优点在于吞吐量比公平锁大。

synchronized也是一种非公平锁。

二、可重入锁(又名递归锁)

2.1 可重入锁是什么

可重入锁,也叫做递归锁。指的是同一线程外层函数获得锁之后,内层递归函数仍然能够获得该锁的代码。

在同一个线程,在外层方法获取锁的时候,进入内层方法时会自动获取锁。

也就是,线程可以进入任何一个它已经拥有的锁 所同步着的代码块。

ReentrantLock/Synchrnoized就是一个典型的可重入锁。

可重入锁最大的作用就是避免死锁。

2.2 synchronized

package com.yuxx.lock;

import java.util.concurrent.TimeUnit;

class Phone{

public synchronized void sendSMS() throws Exception{

System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");

TimeUnit.SECONDS.sleep(2);

sendEmail();

}

public synchronized void sendEmail() throws Exception{

System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");

}

}

/**

case one synchronized典型的可重入锁

t1 invoked sendSMS() t1在外层方法获取锁的时候

t1 invoked sendEmail() t1在进入内层方法会自动获取锁

t2 invoked sendSMS()

t2 invoked sendEmail()

*/

public class ReentrantLockDemo {

public static void main(String[] args) {

Phone phone = new Phone();

new Thread(() -> {

try {

phone.sendSMS();

} catch (Exception e) {

e.printStackTrace();

}

},"t1").start();

new Thread(() -> {

try {

phone.sendSMS();

} catch (Exception e) {

e.printStackTrace();

}

},"t2").start();

}

}

2.3 ReentrantLock

package com.yuxx.lock;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

class Phone implements Runnable{

Lock lock = new ReentrantLock();

@Override

public void run() {

get();

}

public void get() {

lock.lock();

try {

System.out.println(Thread.currentThread().getName() + "\t invoked get()");

set();

} finally {

lock.unlock();

}

}

public void set() {

lock.lock();

try {

System.out.println(Thread.currentThread().getName() + "\t invoked set()");

} finally {

lock.unlock();

}

}

}

/**

case two ReentrantLock

t3 invoked get()

t3 invoked set()

t4 invoked get()

t4 invoked set()

*/

public class ReentrantLockDemo {

public static void main(String[] args) {

Phone phone = new Phone();

Thread t3 = new Thread(phone,"t3");

Thread t4 = new Thread(phone,"t4");

t3.start();

t4.start();

}

}

三、自旋锁

3.1 什么是自旋锁

自旋锁(SpinLock)是指:尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁。

这样的好处是:减少线程上下文切换的消耗,缺点是循环会消耗CPU。

CAS算法就采用了自旋锁的方式,Unsafe类的getAndAddInt方法就是一个典型的自旋锁。

3.2 实现一个自旋锁

package com.yuxx.lock;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicReference;

/**

* 题目:实现一个自旋锁

* 自旋锁好处:循环比较获取直至成功为止,没有类似wait的阻塞。

*

* 通过CAS操作完成自旋锁,A线程先进来调用myLock方法,自己持有5秒钟,

* B随后进来后发现,当前线程持有锁,不是null,

* 所以只能通过自旋等待,直到A释放锁后B随后抢到。

*/

public class SpinLockDemo {

AtomicReference atomicReference = new AtomicReference<>();

public void myLock(){

Thread thread = Thread.currentThread();

System.out.println(thread.getName() + "\t come in!");

while(!atomicReference.compareAndSet(null,thread)){

}

}

public void myUnlock(){

Thread thread = Thread.currentThread();

atomicReference.compareAndSet(thread,null);

System.out.println(thread.getName() + "\t invoked myUnlock()");

}

public static void main(String[] args) {

SpinLockDemo spinLockDemo = new SpinLockDemo();

new Thread(()->{

spinLockDemo.myLock();

try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}

spinLockDemo.myUnlock();

},"AA").start();

new Thread(()->{

spinLockDemo.myLock();

spinLockDemo.myUnlock();

},"BB").start();

}

}

Demo说明:AA和BB线程,先后进入myLock方法。AA进入myLock方法,打印“AA    come in!”,然后进入while循环,通过自旋锁,将atomicReference的引用指向thread,随后AA线程阻塞5秒。B进入后,打印“BB    come in!”,然后进入while循环,第一次比较并判断,atomicReference指向的是thread,并不是null,所以继续比较并判断。直到AA线程阻塞完毕,调用了myUnlock方法,将atomicReference引用再指回null,并打印“AA    invoked myUnlock()”。如此,BB线程的CAS,检测到atomicReference引用指向了null,会再将atomicReference比较并替换,指向thread。这样,BB线程的myLock方法调用完毕,开始调用myUnlock方法,将atomicReference引用比较并替换,指向null,再打印出“BB    invoked myUnlock()”。至此,程序执行完毕。

四、独占锁(写)/共享锁(读)/互斥锁

4.1 介绍

独占锁:指该锁一次只能被一个线程所持有。ReentrantLock和synchronized都是独占锁。

共享锁:指该锁可被多个线程所持有。

ReentrantReadWriteLock的读锁是共享锁,写锁时是独占锁。

读锁的共享锁可以保证并发读是非常高效的,读写、写读、写写的过程都是互斥的。

4.2 ReadWriteLockDemo

package com.yuxx.lock;

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache{

private volatile Map map = new HashMap<>();

//实现ReadWriteLock接口(不是Lock的实现类)

private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

public void put(String key,Object value){

rwLock.writeLock().lock();

try {

System.out.println(Thread.currentThread().getName() + "\t正在写入:" + key);

//模拟网络延迟

try {

TimeUnit.MILLISECONDS.sleep(300);

} catch (InterruptedException e) {

e.printStackTrace();

}

map.put(key,value);

System.out.println(Thread.currentThread().getName() + "\t写入完成!");

}catch (Exception e){

e.printStackTrace();

}finally {

rwLock.writeLock().unlock();

}

}

public void get(String key){

rwLock.readLock().lock();

try {

System.out.println(Thread.currentThread().getName() + "\t正在读取:");

//模拟网络延迟

try {

TimeUnit.MILLISECONDS.sleep(300);

} catch (InterruptedException e) {

e.printStackTrace();

}

Object result = map.get(key);

System.out.println(Thread.currentThread().getName() + "\t读取完成:" + result);

} catch (Exception e) {

e.printStackTrace();

} finally {

rwLock.readLock().unlock();

}

}

}

/**

* 多个线程同时读一个资源类没有问题,所以为了满足并发量,读取共享资源应该可以同时进行。

* 但是

* 如果有一个线程想去写共享资源,就不能再有其他线程可以对该资源进行读或写

* 小总结:

* 读-读 能共存

* 读-写 不能共存

* 写-写 不能共存

*

* 写操作:原子+独占,整个过程必须是一个完整的统一体,中间不允许被分割被打断。

*/

public class ReadWriteLockDemo {

public static void main(String[] args) {

MyCache myCache = new MyCache();

for (int i = 1; i <= 5; i++) {

System.out.println("W-"+i);

final int tempInt = i;

new Thread(()->{

myCache.put(String.valueOf(tempInt),String.valueOf(tempInt));

},"W-"+String.valueOf(i)).start();

}

for (int i = 1; i <= 5; i++) {

final int tempInt = i;

new Thread(()->{

myCache.get(String.valueOf(tempInt));

},"R-"+String.valueOf(i)).start();

}

}

}

打印结果:

你可能感兴趣的:(自旋锁和互斥锁实例)