Synchronized和Lock的区别



Synchronized和Lock的区别

1.前言

早期的时候我们对线程的主要操作为:synchronized wati notify

然后后面出现了替代方案:lock await singal

2.synchronized 和 lock 有什么区别?用新的lock有什么好处?举例说明

1)synchronized属于JVM层面,属于java的关键字

  • monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象 只能在同步块或者方法中才能调用 wait/ notify等方法)
  • Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁

2)使用方法:

  • synchronized:不需要用户去手动释放锁,当synchronized代码执行后,系统会自动让线程释放对锁的占用
  • ReentrantLock:则需要用户去手动释放锁,若没有主动释放锁,就有可能出现死锁的现象,需要lock() 和 unlock() 配置try catch语句来完成

3)等待是否中断

  • synchronized:不可中断,除非抛出异常或者正常运行完成
  • ReentrantLock:可中断,可以设置超时方法
    • 设置超时方法,trylock(long timeout, TimeUnit unit)
    • lockInterrupible() 放代码块中,调用interrupt() 方法可以中断

4)加锁是否公平

  • synchronized:非公平锁
  • ReentrantLock:默认非公平锁,构造函数可以传递boolean值,true为公平锁,false为非公平锁

5)锁绑定多个条件Condition

  • synchronized:没有,要么随机,要么全部唤醒
  • ReentrantLock:用来实现分组唤醒需要唤醒的线程,可以精确唤醒,而不是像synchronized那样,要么随机,要么全部唤醒

lock使用

1.案例一

当A线程执行完后,B线程才能执行,然后B线程执行完成后,C线程才执行

首先我们需要创建一个 重入锁

1
2
// 创建一个重入锁
private Lock lock = new ReentrantLock();

然后定义 三个条件,也可以称为锁的钥匙,通过它就可以获取到锁,进入到方法里面

1
2
3
4
// 这三个相当于备用钥匙
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();

然后开始记住锁的三部曲: 判断 干活 唤醒

这里的判断,为了避免虚假唤醒,一定要 采用 while

干活就是把需要的内容,打印出来

唤醒的话,就是修改资源类的值,然后精准唤醒线程进行干活:这里A 唤醒B, B唤醒C,C又唤醒A(链式调用)

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* Lock使用
* 题目:多线程之间按顺序调用,实现 A-> B -> C 三个线程启动,要求如下:
* AA打印5次,BB打印10次,CC打印15次
* 紧接着
* AA打印5次,BB打印10次,CC打印15次
* ..
* 来10轮
* @author coderblue
*/
public class SyncAndReentrantLockDemo {

public static void main(String[] args) {

ShareResource shareResource = new ShareResource();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareResource.print5();
}
}, "A").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareResource.print10();
}
}, "B").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareResource.print15();
}
}, "C").start();
}
}

class ShareResource {
// A 1 B 2 c 3
private int number = 1;
// 创建一个重入锁
private Lock lock = new ReentrantLock();

// 这三个相当于备用钥匙
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();


public void print5() {
lock.lock();
try {
// 判断:为了避免虚假唤醒,一定要采用 while
while(number != 1) {
// 不等于1,需要等待
condition1.await();
}

// 干活
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
}

// 唤醒 (干完活后,需要通知B线程执行)
number = 2;
// 通知2号去干活了
condition2.signal();

} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void print10() {
lock.lock();
try {
// 判断
while(number != 2) {
// 不等于2,需要等待
condition2.await();
}

// 干活
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
}

// 唤醒 (干完活后,需要通知C线程执行)
number = 3;
// 通知3号去干活了
condition3.signal();

} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void print15() {
lock.lock();
try {
// 判断
while(number != 3) {
// 不等于3,需要等待
condition3.await();
}

// 干活
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
}

// 唤醒 (干完活后,需要通知C线程执行)
number = 1;
// 通知1号去干活了
condition1.signal();

} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
A	 1	0
A 1 1
A 1 2
A 1 3
A 1 4
B 2 0
B 2 1
B 2 2
B 2 3
B 2 4
B 2 5
B 2 6
B 2 7
B 2 8
B 2 9
C 3 0
C 3 1
.....

synchronized使用

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
35
36
37
38
39
40
41
42
/**
* 同步代码块传参变量对象 (锁住的是变量对象)
*
* @author coderblue
*/

public class SynchronizedTest {
public static Integer lockObject;

public SynchronizedTest(Integer lockObject) {
SynchronizedTest.lockObject = lockObject;
}

/**
* 锁住了实例中的成员变量: 如果不加static,那么锁住的就是这个线程下对应创建对象时初始的lockObject值
*/
public void test3() {
synchronized (lockObject) {
try {
System.out.println((Thread.currentThread().getName() + " test3 进入了同步块"));
Thread.sleep(500);
System.out.println((Thread.currentThread().getName() + " test3 休眠结束" + --SynchronizedTest.lockObject));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SynchronizedTest st = new SynchronizedTest(127);
SynchronizedTest st2 = new SynchronizedTest(120);
new Thread(() -> {
System.out.println((Thread.currentThread().getName() + " test 准备进入"));
st.test3();
}).start();
new Thread(() -> {
System.out.println((Thread.currentThread().getName() + " test 准备进入"));
st2.test3();
}).start();

}

}

输出

1
2
3
4
5
6
Thread-0 test 准备进入
Thread-0 test3 进入了同步块
Thread-1 test 准备进入
Thread-0 test3 休眠结束119
Thread-1 test3 进入了同步块
Thread-1 test3 休眠结束118

2.锁静态方法(全局锁):所有调用该方法的线程都会实现同步

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
/**
* 修饰静态方法(全局锁):所有调用该方法的线程都会实现同步
* @author coderblue
*/
public class SynchronizedMethedTest {

//全局锁,静态方法全局唯一的
public synchronized static void test5() {
try {
System.out.println(Thread.currentThread().getName() + " test5 进入了同步块");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " test5 退出了同步块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
test5();
}).start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
test5();
}).start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
SynchronizedMethedTest.test5();
}).start();
}
}

输出

1
2
3
4
5
6
7
8
9
Thread-0 test 准备进入
Thread-0 test5 进入了同步块
Thread-1 test 准备进入
Thread-2 test 准备进入
Thread-0 test5 退出了同步块
Thread-2 test5 进入了同步块
Thread-2 test5 退出了同步块
Thread-1 test5 进入了同步块
Thread-1 test5 退出了同步块

3.同步代码块传参class对象(全局锁)

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
/**
* 同步代码块传参class对象(全局锁)
* @author coderblue
*/
public class SynchronizedClassTest {

//全局锁,类是全局唯一的
public void test4() {
synchronized (SynchronizedClassTest.class) {
try {
System.out.println(Thread.currentThread().getName() + " test4 进入了同步块");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " test4 退出了同步块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
SynchronizedClassTest st = new SynchronizedClassTest();
SynchronizedClassTest st2 = new SynchronizedClassTest();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
st.test4();
}).start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
st2.test4();
}).start();
}
}

输出

1
2
3
4
5
6
Thread-0 test 准备进入
Thread-1 test 准备进入
Thread-0 test4 进入了同步块
Thread-0 test4 退出了同步块
Thread-1 test4 进入了同步块
Thread-1 test4 退出了同步块
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  1. © 2020-2021 Lauy    湘ICP备20003709号

请我喝杯咖啡吧~

支付宝
微信