对公平锁、非公平锁、可重入锁、递归锁、自旋锁的理解

 本篇文章主要是记录自己的学习笔记,主要内容是:公平锁、非公平锁、可重入锁、递归锁、自旋锁的理解,并实现一个自旋锁。

一、公平和非公平锁

(1)公平锁和非公平锁是什么?

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

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

(2)公平锁和非公平锁的区别是什么?

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

公平锁和非公平锁两者的区别:

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

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

补充:ReentrantLock即使通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点就是吞吐量比公平锁大。同样的synchronized也是一种非公平锁。

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

可重入锁定义:指的是同一线程外层函数获得锁忠厚,内层递归函数仍能获取该锁。在同一个线程在外层党法获取锁的时候,在进入内层方法会自动获取锁。也就是说。线程可以进入任何一个它已经拥有的锁所同步着的代码块。

ReetrantLock和synchronized就是一个典型的可重入锁,其最大的作用就是避免死锁

下面我们用代码来进行验证:

class Phone{
    public synchronized void sendMessage(){
        System.out.println(Thread.currentThread().getId() + " \t " + " invoked sendMessage");
        sendEmail();
    }

    public synchronized void sendEmail(){
        System.out.println(Thread.currentThread().getId() + " \t " + " ###### invoked sendEmail");
        System.out.println("----------------------------------- 分割线  -----------------------------------");
        System.out.println();
    }
}
public class ReetrantLockDemo {

    public static void main(String[] args) {

        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMessage();
        }).start();

        new Thread(()->{
            phone.sendMessage();
        }).start();
    }
}

 输出结果:

对于RetrantLock同样可得上述结果:

class Phone{

    Lock lock = new ReentrantLock();

    public void sendMessage(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getId() + " \t " + " invoked sendMessage");
            sendEmail();
        }finally {
            lock.unlock();
        }

    }

    public synchronized void sendEmail(){
        try {
            lock.lock();

            System.out.println(Thread.currentThread().getId() + " \t " + " ###### invoked sendEmail");
            System.out.println("----------------------------------- 分割线  -----------------------------------");
            System.out.println();
        }finally {
            lock.unlock();
        }

    }
}
public class ReetrantLockDemo {

    public static void main(String[] args) throws InterruptedException {

        Phone phone = new Phone();

        new Thread(()->{
            phone.sendMessage();
        }).start();

        new Thread(()->{
            phone.sendMessage();
        }).start();
    }
}

 输出结果同上。

三、自旋锁(spinlock)

自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是:循环会消耗CPU资源。

我们学习过的CAS原理,就是采用了自旋锁的思想:

 了解了自旋锁的原理之后,我们自己实现一个自旋锁。

public class SpinLockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void myLock(){
        System.out.println(Thread.currentThread().getName() + "\t come in");
        Thread thread = Thread.currentThread();
        while (!atomicReference.compareAndSet(null, thread)){

        }
    }

    public void myUnLock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t leave out");
    }
    public static void main(String[] args) {
        SpinLockDemo demo = new SpinLockDemo();

        new Thread(()->{
            demo.myLock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            demo.myUnLock();
        }, "AAA").start();

        new Thread(()->{
            demo.myLock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            demo.myUnLock();
        }, "BBB").start();
    }
}

 输出结果:

 

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

独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和synchronized来说,它们都是独占锁。

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

对ReentrantReadWriteLock来说,读锁是共享锁,写锁是独占锁。

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

  也就是说,多个线程同时读取一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源,就不应该又其他的线程对资源进行读或者写。

总结:读-读可共存

     读-写不可以共存

   写-写不可以共存

代码实现小例子:

class CacheResource{
    private volatile Map<String, Object> map = new HashMap<>();
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value){
        try {
            reentrantReadWriteLock.writeLock().lock();
            System.out.println("--------------------------------------------------------------");
            System.out.println(Thread.currentThread().getId() + " \t " + "正在写入" + key);

        }finally {
            System.out.println(Thread.currentThread().getId() + " \t " + "写入成功" + key);
            System.out.println("--------------------------------------------------------------");

            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    public void get(String key){
        try {
            reentrantReadWriteLock.readLock().lock();
            System.out.println(Thread.currentThread().getId() + " \t " + "正在读取 \t" + key);
        }finally {
            System.out.println(Thread.currentThread().getId() + " \t " + "读取成功 \t" + key);
            reentrantReadWriteLock.readLock().unlock();
        }

    }
}
public class ReentrantReadWriteLockDemo {

    public static void main(String[] args) {
        CacheResource resource = new CacheResource();

        for (int i = 0; i < 5; i++){
            final int tmp = i;
            new Thread(()->{
                resource.put(tmp + "", "");
            }).start();
        }

        for (int i = 0; i < 5; i++){
            final int tmp = i;
            new Thread(()->{
                resource.get(tmp + "");
            }).start();
        }
    }
}

输出结果:

--------------------------------------------------------------
12      正在写入1
12      写入成功1
--------------------------------------------------------------
--------------------------------------------------------------
11      正在写入0
11      写入成功0
--------------------------------------------------------------
--------------------------------------------------------------
13      正在写入2
13      写入成功2
--------------------------------------------------------------
--------------------------------------------------------------
14      正在写入3
14      写入成功3
--------------------------------------------------------------
--------------------------------------------------------------
15      正在写入4
15      写入成功4
--------------------------------------------------------------
16      正在读取     0
16      读取成功     0
18      正在读取     2
18      读取成功     2
19      正在读取     3
19      读取成功     3
20      正在读取     4
20      读取成功     4
17      正在读取     1
17      读取成功     1

Process finished with exit code 0

 到这里,本篇文章就结束了,虽然写博客会花费一定的时间,但是可以加深自己对知识点的理解,便于日后复习,要坚持。

猜你喜欢

转载自www.cnblogs.com/ch-forever/p/10779699.html