Java基础-0x05:Java之锁的使用

ss

JUC

Java 锁的使用

  • 公平锁/非公平锁
  • 可重入锁(递归锁)
  • 读锁/写锁

公平锁和非公平锁

是什么公平锁和非公平锁

  • 公平锁

    先来后到,像是一个队列

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

  • 非公平锁

    允许加塞

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

公平锁和非公平锁的区别

并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是booleanfalse的非公平锁。

  • 公平锁

    Threads acquire a fair lock in the order in which they requested it.

    在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程时等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO(FirstInFirstOut)的规则从队列中取到自己。

  • 非公平锁

    A nonfair lock premits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lock happens to be available it is requested.

    请求锁时尝试占用锁,如果占用失败,再尝试公平锁的方式。

对于ReentrantLock而言,非公平锁比公平锁吞吐量大。

Synchronized 也是一种非公平锁。

可重入锁

什么是可重入锁

可重入锁(又名递归锁

指的是同一线程外层函数获得锁之后,内侧递归函数仍然能获得该锁的代码。在同一个线程在外侧方法获取锁的时候,再进入内层方法会自动获取锁。

也就是说,线程可以进入任何一个他已经拥有的锁所同步着的代码块。

1
2
3
4
5
6
7
public sync void method0x00(){
methor0x01();
}

public sync void method0x01(){
···
}

在一个同步方法中访问另一个同步方法,两个方法用的是同一把锁

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

可重入锁的最大作用

避免死锁

case 1:

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
/**
* @author allwayz
*/
public class SynchronizedDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();

new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}

class Phone{
public synchronized void sendSMS() throws Exception{
System.out.println(Thread.currentThread().getName() + "\t Invoked sendSMS");
sendEmail();
}

public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName() + "\t Invoked sendEmail");
}
}

运行结果:

1
2
3
4
5
6
/**
* t1 Invoked sendSMS t1线程在外层方法获取锁
* t1 Invoked sendEmail t1在进入内层方法会自动获取锁
* t2 Invoked sendSMS
* t2 Invoked sendEmail
*/

case2:

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
/**
* @author allwayz
*/
public class ReentrantLockDemo {
public static void main(String[] args) {
ResourceModel resourceModel = new ResourceModel();
Thread t1 = new Thread(resourceModel);
Thread t2 = new Thread(resourceModel);
t1.start();
t2.start();
}
}

/**
* 对资源类实现Runnable接口
*/
class ResourceModel implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}

public void get(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t Invoked get()");
set();
}finally {
lock.unlock();
}
}

public void set(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t Invoked set()");
}finally {
lock.unlock();
}
}
}

运行结果

1
2
3
4
5
6
/**
* Thread-0 Invoked get()
* Thread-0 Invoked set()
* Thread-1 Invoked get()
* Thread-1 Invoked set()
*/

自旋锁(spinlock)

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

1
2
3
4
5
6
7
8
//unsafe.getAndAddInt()
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}

SpinLockDemo

手写自旋锁

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
/**
* 题目: 手写一个自旋锁<br>
*
* 自旋锁的好处: 循环比较获取,直到成功为止,没有类似wait的阻塞。
* muLock()方法: 在while loop中调用compareAndSet(),
* 如果期望值线程是null,就说明我是第一个进入的线程。
* myUnLock()方法: 继续调用compareAndSet(),如果期望值thread,
* 实际也是thread,就把原子引用线程设置为空,等待下一次上锁。
*
* @author allwayz
*/
public class SpinLockDemo {
/**
* 原子引用线程
*/
AtomicReference<Thread> atomicReference = new AtomicReference<>();

public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();

new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
},"0x00").start();

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

new Thread(()->{
spinLockDemo.myLock();
spinLockDemo.myUnLock();
},"0x01").start();

}

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

}
}

public void myUnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t Invoked myUnLock");
}
}

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

  • 独占锁

    指该锁一次只能被一个线程持有。对ReentranLockSynchronized而言都是独占锁

  • 共享锁

    指该锁可悲多个线程持有

    ReentrantReadWriteLock其读锁是共享锁,写锁是独占锁

    读锁的共享锁可保证并发读,是非常高效的。

    读写写读写写的过程是互斥的。

接下来手写一个读写锁

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* 题目:手写读写锁<br>
* <ul>
* <li>多个线程读去同一个资源类</li>
* <li>只能有一个线程对资源类进行写操作</li>
* </ul>
* <hr>
* <ul>
* <li>写操作:原子➕独占</li>
* <li>读操作:并行</li>
* </ul>
*
* @author allwayz
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MessageInfo messageInfo = new MessageInfo();

for (int i = 1; i <= 5; i++) {

int finalI = i;
new Thread(() -> {
messageInfo.write(String.valueOf(finalI), finalI);
messageInfo.read(String.valueOf(finalI));
},String.valueOf(i)).start();
}
}
}

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

public void write(String key, Object value){
reentrantReadWriteLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()
+ "\t Writing: "+ key);
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key,value);
System.out.println(Thread.currentThread().getName()
+ "\t writing complete");
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantReadWriteLock.writeLock().unlock();
}
}

public void read(String key){
reentrantReadWriteLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()
+ "\t Loading... " + key);
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()
+ "\t Reading:" + result);
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantReadWriteLock.readLock().unlock();
}
}

public void flush(){
map.clear();
}
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 1 Writing: 1
* 1 writing complete
* 2 Writing: 2
* 2 writing complete
* 3 Writing: 3
* 3 writing complete
* 4 Writing: 4
* 4 writing complete
* 5 Writing: 5
* 5 writing complete
* 5 Loading... 5
* 1 Loading... 1
* 2 Loading... 2
* 4 Loading... 4
* 3 Loading... 3
* 3 Reading:3
* 5 Reading:5
* 2 Reading:2
* 1 Reading:1
* 4 Reading:4
*/

写入的时候不会被打断,读取的时候可以一起读取