java spin lock_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 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 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\tlock%n", Thread.currentThread().getName());

try {

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

spinLock.unlock();

System.out.printf("%s\tunlock%n", Thread.currentThread().getName());

}, "t1").start();

TimeUnit.SECONDS.sleep(1);

new Thread(() -> {

spinLock.lock();

System.out.printf("%s\tlock%n", Thread.currentThread().getName());

spinLock.unlock();

System.out.printf("%s\tunlock%n", Thread.currentThread().getName());

}, "t2").start();

}

}

## 读写锁

在java中,ReentrantLock和synchronized都是独占锁,也就是当一个线程获取到锁后,其他线程会阻塞。

使用ReentrantLock和synchronized可以保证线程获取和写入资源的安全,但是在读多写少的场景下,如果每次对读操作都加上独占锁,那么会降低读的性能。

在此基础上,有了读写锁的应用场景,一般的缓存操作(读缓存和写缓存),更适合用读写锁。

### 测试代码

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.locks.ReentrantReadWriteLock;

import java.util.stream.IntStream;

class Cache {

private volatile Map map = new HashMap<>();//对于线程共同操作的资源,需要加上volatile

private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();//读写锁

public void put(Integer num, Object o) {

lock.writeLock().lock();

try {

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

map.put(num, o);

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

} finally {

lock.writeLock().unlock();

}

}

public void get(Integer num) {

lock.readLock().lock();

try {

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

Object o = map.get(num);

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

} finally {

lock.readLock().unlock();

}

}

}

public class Main {

public static void main(String[] args) {

Cache cache = new Cache();

IntStream.range(0, 10).forEach(i -> {

new Thread(() -> cache.put(i, i), String.valueOf(i)).start();

});

IntStream.range(0, 10).forEach(i -> {

new Thread(() -> cache.get(i), String.valueOf(i)).start();

});

}

}

输出

0正在写入,0:0

0写入完成

5正在写入,5:5

5写入完成

1正在写入,1:1

1写入完成

7正在写入,7:7

7写入完成

2正在写入,2:2

2写入完成

3正在写入,3:3

3写入完成

4正在写入,4:4

4写入完成

6正在写入,6:6

6写入完成

8正在写入,8:8

8写入完成

9正在写入,9:9

9写入完成

0正在读取,0

0读取完成,0:0

1正在读取,1

2正在读取,2

2读取完成,2:2

1读取完成,1:1

4正在读取,4

4读取完成,4:4

3正在读取,3

6正在读取,6

7正在读取,7

7读取完成,7:7

5正在读取,5

8正在读取,8

6读取完成,6:6

3读取完成,3:3

8读取完成,8:8

9正在读取,9

5读取完成,5:5

9读取完成,9:9

由结果可见,在线程进行写操作时,其操作是没有被阻塞的。

在线程进行读操作时,可以由多个线程来同时读取。

ReentrantReadWriteLock的特性

①具有与ReentrantLock类似的公平锁和非公平锁的实现:默认的支持非公平锁,对于二者而言,非公平锁的吞吐量由于公平锁;

②支持重入:读线程获取读锁之后能够再次获取读锁,写线程获取写锁之后能再次获取写锁,也可以获取读锁。

③锁能降级:遵循获取写锁、获取读锁在释放写锁的顺序,即写锁能够降级为读锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值