公平锁和非公平锁

公平锁:多个线程按照申请所得顺序来获取锁,先进先出。如果等待队列为空或者在第一个位置则直接占有锁
非公平锁: 线程直接抢占锁,如果抢占失败,则加入等待队列,采用公平锁方式

非公平锁优势就是吞吐量比非公平锁大。synchronizedReentrantLock默认都是非公平锁。ReentrantLock在构造的时候传入true则是公平锁。


可重入锁(递归锁)

可重入锁(也就是递归锁):指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。synchronizedReentrantLock都是常见的可重入锁。

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

可重入锁的作用就是避免死锁的问题。

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
/*
* 可重入锁(也就是递归锁):指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,
* 在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。也就是说,线程可以进入任何一个
* 它已经拥有的锁所有同步着的代码块。
* */
class Phone{
public synchronized void sendSMS() throws Exception{
System.out.println(Thread.currentThread().getId()+"\t invoked sendSMS()");
sendEmail();
}

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

public class RenenterLockDemo {
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();
}

}

实现一个可重入锁(递归锁)

为每个锁关联一个获取计数器和一个所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程所占有的。当线程请求一个未被持有的锁时,计数值将会递增。而当线程退出同步代码时,计数器会相应地递减。当计数值为0时,则释放该锁。

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
public class SpinLockDemo {
private MySpinLock lock = new MySpinLock();
class Widget{
public void doSomething(){
lock.lock();
System.out.println("Widget calling doSomething");
lock.unlock();
}
}

class MySpinLock{
private AtomicReference<Thread> owner = new AtomicReference<>();
private int count = 0;
public void lock(){
Thread cur = Thread.currentThread();
if (cur == owner.get()){
count ++;
return;
}
while (! owner.compareAndSet(null,cur)){

}
}

public void unlock(){
Thread cur = Thread.currentThread();
if (cur == owner.get()){
if (count != 0){
count --;
} else {
owner.compareAndSet(cur,null);
}
}
}
}

class LoggingWidget extends Widget {
@Override
public void doSomething() {
lock.lock();
System.out.println("LoggingWidget calling doSomething");
super.doSomething();
lock.unlock();
}
}

public static void main(String[] args){
SpinLockDemo spinLockDemo = new SpinLockDemo();
SpinLockDemo.Widget widget = spinLockDemo.new LoggingWidget();
widget.doSomething();
}
}

输出结果:
LoggingWidget calling doSomething
Widget calling doSomething

独占锁和共享锁

读锁是共享的,写锁是独占的ReentrantLocksynchronized都是独占锁,独占锁就是一个锁只能被一个线程所持有。有的时候,需要读写分离,那么就要引入读写锁,即ReentrantReadWriteLock

比如缓存,就需要读写锁来控制。缓存就是一个键值对,读的get方法使用了ReentrantReadWriteLock.ReadLock(),写的put方法使用了ReentrantReadWriteLock.WriteLock()。这样避免了写被打断,实现了多个线程同时读。

DEMO:

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
/*
* 多个线程同时读一个资源类没有问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是写共享
* 资源只能有一个线程。
*
* 写操作:原子+独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断。
* */

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
// private Lock lock = new ReentrantLock();
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public void put(String key,Object value){

reentrantReadWriteLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"\t 正在写入:"+key);
try{
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {e.printStackTrace();}
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"\t 写入完成");
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantReadWriteLock.writeLock().unlock();
}

}

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

}

}

public class ReadWriteLockDemo {
public static void main(String[] args){
MyCache myCache = new MyCache();
for(int i=1;i<=5;i++){
final int tempInt = i;
new Thread(()->{
myCache.put(tempInt+"",tempInt+"");
},String.valueOf(i)).start();
}

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

自旋锁

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下切换的消耗,缺点是循环会消耗CPU。CAS底层的getAndAddInt就是自旋锁思想。

尝试写个自旋锁

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
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/*
* 写一个自旋锁
* 自旋锁的好处:循环比较获取直到成功为止,没有类似wait的阻塞。
* */
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();

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

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

public static void main(String[] args){
// 原子引用线程
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try{ TimeUnit.SECONDS.sleep(30); } catch (InterruptedException e) {e.printStackTrace();}
spinLockDemo.myUnLock();

},"AA").start();

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

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

synchronized和lock有什么区别?用新的lock有什么好处?

  • 原始构成

synchronized是关键字,属于JVM层面,monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象只有在同步块或者方法中才能调用wait/notify等方法)

Lock是具体类(java.util.concurrent.locks.lock)api层面的锁。

  • 使用方法

synchronized不需要用户去手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用。

ReentrantLock则需要用户去手动释放锁,若没有主动释放锁,就有可能导致出现死锁现象。需要lock()unlock()方法配合try/finally语句块来完成。

  • 等待是否可中断

synchronized不可中断,除非抛出异常或者正常运行完成。

ReentrantLock可中断
1.设置超时方法 tryLock(long timeout,TimeUnit unit);
2.lockInterruptibly()放代码块中,调用interrupt()方法可中断。

  • 加锁是否公平

synchronized非公平锁

ReentrantLock两者都可以,默认非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁。

  • 锁绑定多个条件Condition

synchronized没有

ReentrantLock用来实现分组唤醒需要唤醒的线程,可以精确唤醒,而不是像synchronized要么随机唤醒一个要么唤醒全部线程。

例子

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
* 题目:多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下:
* A打印5次,B打印10次,C打印15次
* 紧接着
* A打印5次,B打印10次,C打印15次
* 。。。。。
* 打印10轮
* */
class ShareResource{
private int number = 1;//A:1.B:2,C:3
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();

public void print5(){
lock.lock();
try{
//1判断
while(number != 1){
c1.await();
}
//2干活
for(int i=1;i<=5;i++){
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//3通知
number = 2;
c2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}

public void print10(){
lock.lock();
try{
//1判断
while(number != 2){
c2.await();
}
//2干活
for(int i=1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//3通知
number = 3;
c3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}

public void print15(){
lock.lock();
try{
//1判断
while(number != 3){
c3.await();
}
//2干活
for(int i=1;i<=15;i++){
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
//3通知
number = 1;
c1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}




}

public class SyncAndReentrantLockDemo {
public static void main(String[] args){
ShareResource shareResource = new ShareResource();

new Thread(()->{
for(int i=1;i<=10;i++){
shareResource.print5();
}
},"A").start();
new Thread(()->{
for(int i=1;i<=10;i++){
shareResource.print10();
}
},"B").start();
new Thread(()->{
for(int i=1;i<=10;i++){
shareResource.print15();
}
},"C").start();
}
}