JAVA 各种锁机制

Posted 卡比兽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA 各种锁机制相关的知识,希望对你有一定的参考价值。

可重入锁

可重锁是指同一个线程,外层函数获取锁后,内层函数可以自动获取到锁。

java中synchronized和ReentrantLock都是可重入锁。

对于synchronized,其实现机制有jvm实现。

对于ReentrantLock,其继承自父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。

当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。

释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。

测试代码


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 可重入锁
 * 1 synchronized
 * 2 ReentrantLock
 */
public class Main {

    private ReentrantLock lock = new ReentrantLock();

    private synchronized void get() {
        System.out.println(Thread.currentThread().getName() + " invoke get() method");
        set();
    }

    private synchronized void set() {
        System.out.println(Thread.currentThread().getName() + " invoke set() method");
    }

    private void _get() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " invoke _get() method");
            _set();
        } catch (Exception ignored) {

        } finally {
            lock.unlock();
        }
    }

    private void _set(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " invoke _set() method");
        } catch (Exception ignored) {

        } finally {
            lock.unlock();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Main main = new Main();
        new Thread(main::get, "t1").start();
        new Thread(main::get, "t2").start();

        TimeUnit.SECONDS.sleep(1);
        System.out.println();
        System.out.println();
        System.out.println();

        new Thread(main::_get,"t3").start();
        new Thread(main::_get,"t4").start();
    }
}

输出结果

t1 invoke get() method
t1 invoke set() method
t2 invoke get() method
t2 invoke set() method

t3 invoke _get() method
t3 invoke _set() method
t4 invoke _get() method
t4 invoke _set() method

自旋锁

自旋锁(spinlock):当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

优点:

自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快

非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

缺点

若锁被其他线程长时间占用,由于一直死循环,会带来许多性能上的开销。

java实现自旋锁

package demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
    AtomicReference<Thread> reference = new AtomicReference<>();

    public void lock() {
        Thread thread = Thread.currentThread();
        while (!reference.compareAndSet(null, thread)) {
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        reference.compareAndSet(thread, null);
    }

    public static void main(String[] args) throws InterruptedException {
        SpinLock spinLock = new SpinLock();
        new Thread(() -> {
            spinLock.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLock.unlock();
        }, "t2").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            spinLock.lock();
            spinLock.unlock();
        }, "t2").start();
    }
}

实现可重入自选锁

思路为维持一个状态变量,记录当前lock的次数。


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
    AtomicReference<Thread> reference = new AtomicReference<>();

    private AtomicInteger num = new AtomicInteger(0);

    public void lock() {
        Thread thread = Thread.currentThread();
        if (reference.get() == thread) {
            num.incrementAndGet();
            return;//当前锁已经获取了,直接return
        }
        while (!reference.compareAndSet(null, thread)) {
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        if (reference.get() != thread) return;
        if (num.get() > 0) {//多次lock
            num.decrementAndGet();
        } else {
            reference.compareAndSet(thread, null);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SpinLock spinLock = new SpinLock();
        new Thread(() -> {
            spinLock.lock();
            System.out.printf("%s	lock%n", Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLock.unlock();
            System.out.printf("%s	unlock%n", Thread.currentThread().getName());
        }, "t1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            spinLock.lock();
            System.out.printf("%s	lock%n", Thread.currentThread().getName());
            spinLock.unlock();
            System.out.printf("%s	unlock%n", Thread.currentThread().getName());
        }, "t2").start();
    }
}







以上是关于JAVA 各种锁机制的主要内容,如果未能解决你的问题,请参考以下文章

Java 中悲观锁的底层实现机制

AQS:Java 中悲观锁的底层实现机制

Java锁机制梳理与详细介绍

多线程之锁机制

synchronized学习

Redis实现分布式锁(设计模式应用实战)