线程-三

[TOC]

Lock

ReentrantLock

在JDK1.5的版本中新加入了很多特性。其中就有ReentrantLockReentrantLocksynchronized更灵活。功能也更强大。学习线程的过程中也是不可避免的。
new 一个ReentrantLock 返回一个Lock接口对象。
通过lock加锁。unlock解锁。在lock与unlock之间的代码就是同步区域。

使用Condition实现等待/通知

关键字synchronizedwaitnotify方法相结合可以实现等待通知模式,类ReentrantLock也可以实现同样的功能,但是要借助于Condition对象,它有更好的灵活性,比如实现多路通知功能,也就是说在一个lock对象创建多个Condition实例,线程对象可以注册在指定的Condition中,从而可以有选择的进行线程通知,在调度线程上更灵活。
在notify中,被通知的线程是JVM随机选择的。但使用ReentrantLock结合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
public class ThreadCondition {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
new Thread(()->{
service.await();
}).start();
Thread.sleep(1000);
service.signal();
}
}
class Service {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void await(){
try{
lock.lock();
System.out.println("A");
condition.await();
System.out.println("A");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signal(){
try {
lock.lock();
System.out.println("解锁");
condition.signal();
}finally {
lock.unlock();
}
}
}

其中Conditionawait等于Objectwaitsignal等于notify
signalAll等于notifyAll

使用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
public class ThreadConditionSelect {
public static void main(String[] args) throws InterruptedException {
ThreadConditionSelectService service1 = new ThreadConditionSelectService();
new Thread(() -> {
service1.awaitA();
}).start();
new Thread(() -> {
service1.awaitB();
}).start();
Thread.sleep(500);
service1.signalAll_A();
}
}

class ThreadConditionSelectService {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();

public void awaitA() {
try {
lock.lock();
System.out.println("A:wait");
conditionA.await();
System.out.println("A:run");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void awaitB() {
try {
lock.lock();
System.out.println("B:wait");
conditionB.await();
System.out.println("B:run");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void signalAll_A() {
try {
lock.lock();
System.out.println("A:signalAll");
conditionA.signalAll();
System.out.println("A:signalAll");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void signalAll_B() {
try {
lock.lock();
System.out.println("B:signalAll");
conditionB.signalAll();
System.out.println("B:signalAll");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

在这代码中最终只有A被唤醒。
通过这种方式我们可以唤醒指定种类的线程。

公平锁与非公平锁

锁lock分为公平锁与非公平锁。公平锁表示线程获取锁的顺序是安装线程加锁的顺序来分配的,即FIFO先进先出的顺序。而非公平锁则是抢占。随机获取锁。所以可能会有一些线程一直拿不到锁。
通过创建ReentrantLock的构造函数,传入Boolean值设置锁的类型。
bd0c3c3058122d3cde548384342f72e6.png
通过源码,我们可以知道,在不传入值的情况下默认是非公平锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadGPLock {
public static void main(String[] args) {
ThreadGPLockService gpLockService = new ThreadGPLockService();
for (int i = 0; i < 100; i++) {
new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":运行了");
gpLockService.await();
}).start();
}
}
}

class ThreadGPLockService {
private Lock lock = new ReentrantLock(true);
public void await() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + ":获得锁");
} finally {
lock.unlock();
}
}
}

通过结果我们可以看出,当锁是公平锁时,谁先等待那么谁就先获取到锁。而非公平锁则是相互竞争。

ReentrantLock类的方法

getHoldCount()的作用时查询当前线程锁保持锁定的个数。也就是调用lock()方法的次数。
getQueueLength()是返回正在等待获取锁的线程数,表示没有调用await的线程。
getWaitQueueLength()getQueueLength()相反,它表示已经调用了await方法的线程数。
hasQueuedThread(Thread)的作用是查询指定线程释放在等待获取此锁定。
HasQueuedThreads()的作用是查询是否有线程正在等待获取此锁定。
hasWaiters(Condition condition)查询是否有线程正在等待与此锁定有关的condition条件。

lock接口中的方法。

lockInterruptibly()的作用是如果当前线程未被中断,则获取锁定。如果已经被中断则出现异常0

tryLock()的作用是如果锁没被其他线程获取就获得锁。

tryLock(long time, TimeUnit unit)在给定时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。

ReentrantReadWriteLock

ReentrantLock类具有完全排他的效果,在同一时间下只有一个线程在执行lock方法后面的任务,虽然保证了安全性,但是效率相对低下。我们可以使用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
public class ThreadReadWriteLock {
public static void main(String[] args) {
Service service = new Service();
for (int i = 0; i < 10; i++) {
new Thread(()->{
service.read();
}).start();
}
}
static class Service{
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
public void read(){
try {
lock.readLock().lock();
System.out.println("获取读锁:"+Thread.currentThread().getName());
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
}

结果如下:

1
2
3
4
5
6
7
8
9
10
获取读锁:Thread-0
获取读锁:Thread-3
获取读锁:Thread-5
获取读锁:Thread-2
获取读锁:Thread-1
获取读锁:Thread-6
获取读锁:Thread-4
获取读锁:Thread-8
获取读锁:Thread-7
获取读锁:Thread-9

通过lock.readLock().lock()加一个读锁。
通过lock.writeLock().lock()加一个写锁
如果这个锁是一个ReentrantLock锁,则同一时刻只能允许一个线程运行,所以如果有10个线程。每个线程睡眠0.5秒,十个线程则需要睡5秒。在使用读锁之后,所有线程都可以同时运行。
可见效率要高很多。
写线程与ReentrantLock基本上一致。