• lock与synchronized的区别:
    • lock是一个接口,synchronized是关键字。前者可以程序员自己DIY功能,实现自定义同步组件;后者是JVM来维护的。
    • lock需要手动加解锁,synchronized自动完成加解锁。
    • synchronized修饰的代码执行异常时,自动释放线程占有的锁,不会出现死锁;lock异常时需在finally中unlock(),否则死锁。
    • lock可以让线程“在等待锁的过程中(即阻塞时)”响应中断,如tryLock;而synchronized在“等待锁的过程中(即阻塞时)”无法响应中断。
    • synchronized只能是非公平锁,而lock可公平和非公平。
    • lock可实现读写锁。
    • lock锁的范围有局限性,仅适用于代码块范围,而synchronized可以锁住代码块,对象实例,类。
    • lock可以绑定条件,实现分组唤醒需要的线程;synchronized要么随机唤醒一个,要么唤醒全部线程。
1
2
3
4
5
6
7
8
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
1
2
3
4
5
6
7
8
9
Lock lock = new LockImpl;	// new一个Lock的实现类
lock.lock(); // 加锁
try {
// todo
} catch(Exception ex) {
// todo
} finally {
lock.unlock(); // 释放锁
}
  • lock()方法,如果锁被其他线程获取,则进行等待。

    lock的错误使用方法:

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
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ApplicationDemo {
private List<Integer> arrayList = new ArrayList<>();

public static void main(String[] args) {
ApplicationDemo applicationDemo = new ApplicationDemo();

Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
applicationDemo.insert(Thread.currentThread());
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
applicationDemo.insert(Thread.currentThread());
}
});

thread1.start();
thread2.start();
}

private void insert(Thread thread) {
Lock lock = new ReentrantLock();
lock.lock();
try {
System.out.println(thread.getName() + " get lock");
thread.sleep(1000);
} catch(Exception e) {
// ...
} finally {
System.out.println(thread.getName() + " unlock the lock");
lock.unlock();
}
}
}

lock的错误使用方法

错误原因:

insert方法中lock变量为局部变量,每个线程获取到的是不同的锁,所以不会发生冲突。

lock正确使用方式:

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
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ApplicationDemo {
private List<Integer> arrayList = new ArrayList<>();
private Lock lock = new ReentrantLock();

public static void main(String[] args) {
ApplicationDemo applicationDemo = new ApplicationDemo();

Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
applicationDemo.insert(Thread.currentThread());
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
applicationDemo.insert(Thread.currentThread());
}
});

thread1.start();
thread2.start();
}

private void insert(Thread thread) {
lock.lock();
try {
System.out.println(thread.getName() + " get lock");
thread.sleep(1000);
} catch(Exception e) {
// ...
} finally {
System.out.println(thread.getName() + " unlock the lock");
lock.unlock();
}
}
}

lock的正确使用方法

  • tryLock()方法,有返回值,尝试获取锁,获取成功返回true,获取失败(即锁被其他线程获取),返回false。也就是说,该方法无论能否拿到锁,都立即返回,不会拿不到锁的时候一直处于等待状态。
1
2
3
4
5
6
7
8
9
10
11
12
Lock lock = ...;
if (lock.tryLock()) {
try {
// 处理任务
} catch(Exception e) {
...
} finally {
lock.unlock(); // 释放锁
}
} else {
// 如果不能获取锁,则直接做其他事情
}
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
public class Test {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
private Lock lock = new ReentrantLock(); // 注意这个地方
public static void main(String[] args) {
final Test test = new Test();

new Thread() {
public void run() {
test.insert(Thread.currentThread());
}
}.start();

new Thread() {
public void run() {
test.insert(Thread.currentThread());
}
}.start();
}

public void insert(Thread thread) {
if (lock.tryLock()) {
try {
System.out.println(thread.getName() + "得到了锁");
for (int i = 0; i < 5; ++i) {
arrayList.add(i);
}
} catch(Exception e) {
// todo: handle exception
} finally {
System.out.println(thread.getName() + "释放了锁");
lock.unlock();
}
} else {
System.out.println(thread.getName() + "获取锁失败");
}
}
}

// Thread-0得到了锁
// Thread-1获取锁失败
// Thread-0释放了锁
  • lockInterruptibly()方法,可以在线程“等待获取锁”时响应中断,抛出InterruptedException异常。
1
2
3
4
5
6
7
8
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
// ...
} finally {
lock.unlock();
}
}
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
public class Test {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
Test test = new Test();
Mythread thread1 = new MyThread(test);
Mythread thread2 = new MyThread(test);
thread1.start();
thread2.start();

try {
Thread.sleep(2000);
} catch(InterruptedException e) {
e.printStackTrace();
}
thread2.interrupt();
}

public void insert(Thread thread) throws InterruptedException {
lock.lockInterruptibly(); // 注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出
try {
System.out.println(thread.getName() + "得到了锁");
long startTime = System.currentTimeMillis();
for ( ; ; ) {
if (System.currentTimeMillis() - startTime >= Integer.MAX_vALUE)
break;
}
} finally {
System.out.println(Thread.currentThread().getName() + "执行finally");
lock.unlock();
System.out.println(thread.getName() + "释放了锁");
}
}
}

class Mythread extends Thread {
private Test test = null;
public Mythread(Test test) {
this.test = test;
}

@Override
public void run() {
try {
test.insert(Thread.currentThread());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断");
}
}
}

// 运行后发现,子线程可正常被中断
  • ReentrantLock

    可重入锁,即支持一个线程对相同资源重复加锁。实现可重入需解决两个问题:

    1、锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则可再次成功获取。

    2、同一线程对同一资源锁的获取和释放,通过计数的方式来实现。

  • ReadWriteLock读写锁的接口,里面只定义了两个方法,一个用来获取读锁,一个用来获取写锁,即实现两个分开的锁,可分配给多个线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();

/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();
}
  • ReentrantReadWriteLock

    实现了ReadWriteLock接口的类。

    写锁只能一个线程获取;读锁可多个线程获取;有线程读时,写锁等待;有线程写时,读锁等待。

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
// 对于synchronized
public class Test {

public static void main(String[] args) {
final Test test = new Test();

new Thread() {
public void run() {
test.get(Thread,currentThread());
}
}.start();

new Thread() {
public void run() {
test.get(Thread,currentThread());
}
}.start();
}

public synchronized void get(Thread thread) {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName() + "正在进行读操作");
}
System.out.println(thread.getName() + "读操作完毕");
}
}

/*
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0读操作完毕
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-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
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
// 对于读写锁
public class Test {
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

public static void main(String[] args) {
final Test test = new Test();

new Thread() {
public void run() {
test.get(Thread.currentThread());
}
}.start();

new Thread() {
public void run() {
test.get(Thread.currentThread());
}
}.start();
}

public void get(Thread thread) {
rwl.readLock.lock();
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName() + "正在进行读操作");
}
System.out.println(thread.getName() + "读操作完毕");
} finally {
rwl.readLock().unlock();
}
}
}

/*
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0读操作完毕
Thread-1读操作完毕
*/
  • 公平锁

    即按先来后到的顺序让等待的线程获取锁;synchronized就是典型的非公平锁;非公平锁容易造成“饥饿现象”。

    ReentrantLock和ReentrantWriteLock默认为非公平锁,若改为公平锁:

1
ReentrantLock lock = new ReentrantLock(true);
  • Condition条件
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
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ShareResource {
private volatile int num = 1;
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();

// A线程打印1~5
public void print5() {
lock.lock();
try {
while(num != 1) {
conditionA.await();
}
for (int i = 0; i < 5; ++i) {
System.out.println(Thread.currentThread().getName() + " " + (i + 1));
}
num = 2;
conditionB.signal();
} catch(InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

// B线程打印1~10
public void print10() {
lock.lock();
try {
while(num != 2) {
conditionB.await();
}
for (int i = 0; i < 10; ++i) {
System.out.println(Thread.currentThread().getName() + " " + (i + 1));
}
num = 3;
conditionC.signal();
} catch(InterruptedException e) {
e.printStackTrace();
} fianlly {
lock.unlock();
}
}

// C线程打印1~15
public void print15() {
lock.lock();
try {
while(num != 3) {
conditionC.await();
}
for (int i = 0; i < 15; ++i) {
System.out.println(Thread.currentThread().getName() + " " + (i + 1));
}
num = 1;
conditionA.signal();
} catch(InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}


public class ApplicationDemo {
public static void main(String[] args) {
/*
* 锁定多个条件Condition
* 多线程按顺序打印,实现A->B->C的线程执行顺序,循环执行3次
*/
ShareResource shareResource = new ShareResource();

Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; ++i) {
sharedResource.print5();
}
}
}, "A");

Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; ++i) {
sharedResource.print10();
}
}
}, "B");

Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; ++i) {
sharedResource.print15();
}
}
}, "C");

threadA.start();
threadB.start();
threadC.start();
}
}

摘自/参考链接1

摘自/参考链接2

摘自/参考链接3