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 各种锁机制的主要内容,如果未能解决你的问题,请参考以下文章