Java多线程学习笔记(四)

Lock的使用

使用ReentrantLock类

在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增了ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如嗅探锁定,多路分支通知等功能,而且在使用上也比synchronized更加的灵活。

使用ReentrantLock实现同步

ReentrantLock类的使用用例:

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
public class MyService {
private Lock lock = new ReentrantLock();
public void methodA(){
try {
lock.lock();//获取锁
System.out.println("methodA begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("methodA end ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
public void methodB(){
try {
lock.lock();
System.out.println("methodB begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("methodB end ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyService service = new MyService();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}

输出:
methodA begin ThreadName=A time=1503279665595
methodA end ThreadName=A time=1503279670595
methodB begin ThreadName=B time=1503279670596
methodB end ThreadName=B time=1503279675596

使用Condition实现等待/通知

在使用notify()/notifyAll()方法进行通知时,被通知的线程却是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现选择性通知的,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。

而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它的一个对象上。线程开始notifyAll()时,需要通知所有的WAITING对象,没有选择权,会出现相当大的效率问题。

使用多个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
public class MyService {
private Lock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
public void awaitA(){
try {
lock.lock();
System.out.println("begin awaitA 时间为" + System.currentTimeMillis() + "ThreadName=" + Thread.currentThread().getName());
conditionA.await();
System.out.println("end awaitA 时间为" + System.currentTimeMillis() + "ThreadName=" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB(){
try {
lock.lock();
System.out.println("begin awaitB 时间为" + System.currentTimeMillis() + "ThreadName=" + Thread.currentThread().getName());
conditionB.await();
System.out.println("end awaitB 时间为" + System.currentTimeMillis() + "ThreadName=" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalAll_A() {
try {
lock.lock();
System.out.println("signalAll_A 时间为" + System.currentTimeMillis() + "ThreadName=" + Thread.currentThread().getName());
conditionA.signalAll();
}finally {
lock.unlock();
}
}
public void signalAll_B() {
try {
lock.lock();
System.out.println("signalAll_B 时间为" + System.currentTimeMillis() + "ThreadName=" + Thread.currentThread().getName());
conditionB.signalAll();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
Thread.sleep(3000);
service.signalAll_A();
}
}
class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awaitA();
}
}
class ThreadB extends Thread {
private MyService service;
public ThreadB(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awaitB();
}
}

输出:
begin awaitA 时间为1503282953886ThreadName=A
begin awaitB 时间为1503282953889ThreadName=B
signalAll_A 时间为1503282956889ThreadName=main
end awaitA 时间为1503282956890ThreadName=A

我们成功实现等待/通知模式:
Object类中的wait()方法相当于Condition类中的await()方法。
Object类中的notify()方法相当于Condition类中的signal()方法。
Object类中的notifyAll()方法相当于Condition类中的signalAll()方法。

公平锁与非公平锁

公平与非公平锁:锁Lock分为”公平锁”和”非公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果就是不公平的了。默认情况下,ReentrantLock类使用的是非公平锁。

代码实例:

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
public class MyService {
private Lock lock;
public MyService(boolean isFair) {
this.lock = new ReentrantLock(isFair);
}
public void service(){
try {
lock.lock();
System.out.println("Thread Name=" + Thread.currentThread().getName() + "获得锁定");
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService(true);//公平锁
// MyService service = new MyService(false);//非公平锁
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new ThreadA(service);
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
}
}
class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
this.service = service;
}
@Override
public void run() {
System.out.println("*线程" + Thread.currentThread().getName() + "运行了");
service.service();
}
}

其他方法接口

关于ReentrantLock类的使用,有很多辅助方法接口,我们在实际编程使用的时候以Java官方的API为主,这里列举一些简单的方法。

  1. 方法 int getHoldCount() 的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
  2. 方法 int getQueueLength()的作用是返回正等待获取此锁定的线程估计数。
  3. 方法 int getWaitQueueLength(Condition condition)的作用是返回等待与此锁定相关的给定条件Condition的线程估计数。
  4. 方法 boolean hasQueuedThread(Thread thread)的作用是查询指定的线程是否正在等待获取此锁定。
  5. 方法 boolean hasWaiters(Condition condition)的作用是查询是否有线程正在等待与此锁定有关的condition条件。
  6. 方法 boolean isFair()的作用是判断是不是公平锁。
  7. 方法 boolean isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定。
  8. 方法 boolean isLocked(0的作用是查询此锁定是否由任意线程保持。

使用ReentrantReadWriteLock类

类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的。所以在JDK中提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行效率。

读写锁表示也有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程Thread进行写入操作时,进行读取操作的多个Thread都可以获取读锁,而进行写入操作的线程Thread只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。

代码实例:

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
public class MyService {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try {
lock.readLock().lock();
System.out.println("获得读锁=" + System.currentTimeMillis() );
}finally {
lock.readLock().unlock();
}
}
public void write(){
try {
lock.writeLock().lock();
System.out.println("获得写锁=" + System.currentTimeMillis());
}finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService();
ThreadA threadA = new ThreadA(service);
ThreadA threadB = new ThreadA(service);
threadA.start();
threadB.start();
}
}
class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
this.service = service;
}
@Override
public void run() {
service.read();
}
}
class ThreadB extends Thread {
private MyService service;
public ThreadB(MyService service) {
this.service = service;
}
@Override
public void run() {
service.write();
}
}
0%