Java 面试题之锁相关

公平锁/非公平锁

并发包中的ReentrantLock 可以创建公平锁或者非公平锁,默认是非公平锁

Java 多线程值ReentrantLock 与Condition

公平锁

指多个线程按照申请锁的顺序来获取锁

在并发环境中,每个线程都会先查看锁的FIFO 等待队列,若为空,就占有锁,否则,则进队

非公平锁

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

线程一上来就尝试占有锁,若尝试失败,则再采用类似公平锁的那种策略

优点:非公平锁的吞吐量比公平锁大

对于Synchronized 而言,也是一种非公平锁

可重入锁(递归锁)

指的是同一线程在外层函数获得锁之后,内层的递归函数依然可以获得该锁

换句话说:线程可以进入任何一个它已经拥有的锁同步着的代码块

ReentrantLock/Synchronized 是一个典型的可重入锁

可重入锁作用:避免死锁

自旋锁

指的是在尝试获取锁的时候不会立即阻塞,而是采用循环的方式去尝试获取锁

优点:减少上下文的切换

缺点:循环会消耗CPU

典型例子:CAS(AtomicInteger)

读写锁

  • 读-读共存
  • 读-写不共存
  • 写-写不共存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

class MyCache {
private volatile Map<String, Object> map = new HashMap<>(16);
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public Object get(String key) {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "号线程读数据");
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "号线程读数据完毕, " + result);
lock.readLock().unlock();
return result;
}

public void put(String key, Object object) {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "号线程写数据");
map.put(key, object);
System.out.println(Thread.currentThread().getName() + "号线程写数据完毕");
lock.writeLock().unlock();
}
}

public class Test {
public static void main(String[] args) {
MyCache myCache = new MyCache();

for (int i = 0;i < 5;i++) {
final int finalI = i;
new Thread(() -> {
myCache.put(String.valueOf(finalI), String.valueOf(finalI));
}, String.valueOf(i)).start();
}

for (int i = 0;i < 5;i++) {
final int finalI = i;
new Thread(() -> { myCache.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}

输出:
1号线程写数据
1号线程写数据完毕
4号线程写数据
4号线程写数据完毕
0号线程写数据
0号线程写数据完毕
2号线程写数据
2号线程写数据完毕
3号线程写数据
3号线程写数据完毕
0号线程读数据
0号线程读数据完毕, 0
2号线程读数据
1号线程读数据
1号线程读数据完毕, 1
3号线程读数据
3号线程读数据完毕, 3
4号线程读数据
4号线程读数据完毕, 4
2号线程读数据完毕, 2

CountDownLatch

让一些线程阻塞,直到其余线程完成为止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Test {
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch = new CountDownLatch(6);

for (int i = 0;i < 6;i++) {
int finalI = i;
new Thread(() -> {
System.out.println(finalI + "号同学离开");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}

// countDownWatch 达到0 时才能解锁
countDownLatch.await();
System.out.println("班长离开");
}
}

输出:
0号同学离开
4号同学离开
1号同学离开
3号同学离开
2号同学离开
5号同学离开
班长离开

CyclicBarrier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Test {
public static void main(String[] args) throws Exception {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("出发"));

for (int i = 0;i < 7;i++) {
final int index = i;
new Thread(() -> {
System.out.println(index + "号客人到达");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}

输出:
0号客人到达
3号客人到达
2号客人到达
1号客人到达
5号客人到达
4号客人到达
6号客人到达
出发

Semaphore

信号量,控制对共享资源的访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) throws Exception {
Semaphore semaphore = new Semaphore(3);

for (int i = 0; i < 6; i++) {
final int index = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "进站");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "离开");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}

AQS

如果被请求的共享资源处于空闲状态,则当前请求资源的线程就会设置为工作线程,并且将当前资源设置为锁定状态;如果被请求的共享资源处于锁定状态,则需要一套线程阻塞以及线程被唤醒时锁分配的机制,这个机制是使用CLH 队列锁实现的,将暂时获取不到锁的线程放到队列中

典型例子:ReentrantLock, Semaphore, ReentrantReadWriteLock等等

AQS 定义两种资源的共享方式

  • Exclusive(独占):只能有一个线程去独占共享资源,公平锁、非公平锁(Synchronized)
  • Share(共享):多个线程可以同时去执行,如Samephore、CycliBarrier、CountDownWatch

本文标题:Java 面试题之锁相关

文章作者:Enda Lin

发布时间:2019年10月27日 - 10:49

最后更新:2019年10月29日 - 09:52

原始链接:https://wt-git-repository.github.io/2019/10/27/锁相关/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。