Java多线程编程六(使用Lock)

可重入锁ReentrantLock

使用ReentrantLock方法

  • lock():锁定
  • unlock():解除锁定
  • int getHoldCount():返回当前线程保持此锁定的个数,也就是调用lock()方法的次数
  • int getQueueLength():返回正在等待获取此锁定的线程估计数,例如:8个线程,3个线程调用了await()方法,那么在调用此方法后返回的值是5,说明有5个线程同时在等待lock的释放
  • int getWaitQueueLength():顾名思义,其作用是返回等待与此锁定相关的给定条件Condition的线程估计数。例如:8个线程,每个线程都调用了同一个Condition的await()方法,则调用此方法返回的值是8
  • boolean hasQueuedThread():查询指定线程是否正在等待获取此锁定ReentrantLock.hasQueuedThread(thread)
  • boolean hasWaiters():查询是否有线程正在等待与此锁定有关的Condition条件
  • boolean isFair():判断是否是公平锁
  • boolean isHeldByCurrentThread():查询当前线程是否保持此锁定
  • boolean isLocked():查询此锁定是否由任意线程保持
  • void lockInterruptibly():如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常
  • boolean tryLock():仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定
  • boolean tryLock(long timeout, TimeUnit unit):如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定

使用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
public class MyServer {
private ReentrantLock lock = new ReentrantLock();
public void printReentrantLock() {
try {
lock.lock();//获取锁
for (int i = 0; i < 5; i++) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + ",i=" + i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
public static void main(String[] args) {
final MyServer myServer = new MyServer();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
myServer.printReentrantLock();
}
});
thread.setName(String.valueOf(i));
thread.start();
}
}
}

运行结果为:

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
ThreadName=0,i=0
ThreadName=0,i=1
ThreadName=0,i=2
ThreadName=0,i=3
ThreadName=0,i=4
ThreadName=1,i=0
ThreadName=1,i=1
ThreadName=1,i=2
ThreadName=1,i=3
ThreadName=1,i=4
ThreadName=2,i=0
ThreadName=2,i=1
ThreadName=2,i=2
ThreadName=2,i=3
ThreadName=2,i=4
ThreadName=3,i=0
ThreadName=3,i=1
ThreadName=3,i=2
ThreadName=3,i=3
ThreadName=3,i=4
ThreadName=4,i=0
ThreadName=4,i=1
ThreadName=4,i=2
ThreadName=4,i=3
ThreadName=4,i=4

由结果分析知:当前线程获取锁对象后其他线程呈阻塞状态,直到当前线程打印完毕释放锁,其他线程才能获取到锁进行打印。

这个主要进行单个方法同一个锁多线程调用的同步效果,下面来看一下多个线程交叉调用不同方法同一个锁的例子:

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
public class MyServer {

private ReentrantLock lock = new ReentrantLock();

public void methodA() {
try {
lock.lock();//获取锁
System.out.println("method A begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
methodB();//调用methodB
Thread.sleep(5000);
System.out.println("method A end ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void methodB() {
try {
lock.lock();//获取锁
System.out.println("method B begin ThreadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("method B end ThreadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

public static void main(String[] args) {
String[] names = {"A", "AA", "B", "BB"};
final MyServer myServer = new MyServer();
Thread[] threads = new Thread[4];
for (int i = 0; i < 2; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
myServer.methodA();
}
});
}
for (int i = 2; i < 4; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
myServer.methodB();
}
});
}
for (int i = 0; i < 4; i++) {
threads[i].setName(names[i]);
threads[i].start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
method A begin ThreadName=A time=1551406905681
method B begin ThreadName=A time=1551406905681
method B end ThreadName=A time=1551406910685
method A end ThreadName=A time=1551406915689
method A begin ThreadName=AA time=1551406915689
method B begin ThreadName=AA time=1551406915690
method B end ThreadName=AA time=1551406920691
method A end ThreadName=AA time=1551406925691
method B begin ThreadName=B time=1551406925691
method B end ThreadName=B time=1551406930693
method B begin ThreadName=BB time=1551406930693
method B end ThreadName=BB time=1551406935698

分析结果:在线程A获取锁时其他线程呈阻塞状态直至线程A执行完毕且释放锁,其他线程才可以获取到锁,同时在线程A获取锁执行过程中又成功调用了methodB,这些说明reentrantlocksynchronized关键字一样线程之间是同步顺序执行的,也都具有锁重入特性。

使用Condition实现等待/通知

关键字synchronizedwait()notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以试想同样的功能,但需要借助于Condition对象。相对于notifynotifyAllCondition具有跟高的灵活性,在使用notifynotifyAll时,被通知的线程是由JVM随机选择的,而ReentrantLock结合Condition类是可以“选择性通知的”。

Condition中通知/等待方法

  • await():相当于Object类中的wait()方法
  • await(long time, TimeUtil unit):相当于Object类中的wait(long timeout)方法
  • signal():相当于Object类中的notify()方法
  • signalAll():相当于Object类中的notifyAll()方法

简单使用

注意:调用condition.await()之前需要执行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
public class MyServer {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

public void await() {
try {
lock.lock();
System.out.println("await 时间为 " + System.currentTimeMillis());
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void signal() {
try {
lock.lock();
System.out.println("signal 时间为 " + System.currentTimeMillis());
condition.signal();
} finally {
lock.unlock();
}
}

public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();
new Thread(new Runnable() {
public void run() {
myServer.await();
}
}).start();
Thread.sleep(3000);
myServer.signal();
}
}

运行结果为:

1
2
await  时间为 1551420692029
signal 时间为 1551420695031

使用多个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
public class MyServer {
private ReentrantLock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();

@SuppressWarnings("all")
public void awaitA() {
try {
lock.lock();
System.out.println("begin await A 时间为 " + System.currentTimeMillis());
conditionA.await();
System.out.println("end await A 时间为 " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

@SuppressWarnings("all")
public void awaitB() {
try {
lock.lock();
System.out.println("begin await B 时间为 " + System.currentTimeMillis());
conditionB.await();
System.out.println("end await B 时间为 " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

@SuppressWarnings("all")
public void signalAll_A() {
try {
lock.lock();
System.out.println(" signalall A 时间为 " + System.currentTimeMillis());
conditionA.signalAll();
} finally {
lock.unlock();
}
}

@SuppressWarnings("all")
public void signalAll_B() {
try {
lock.lock();
System.out.println(" signalall B 时间为 " + System.currentTimeMillis());
conditionB.signalAll();
} finally {
lock.unlock();
}
}

public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();
Thread threadA = new Thread(new Runnable() {
public void run() {
myServer.awaitA();
}
});
threadA.setName("A");
threadA.start();

Thread threadB = new Thread(new Runnable() {
public void run() {
myServer.awaitB();
}
});
threadB.setName("B");
threadB.start();

Thread.sleep(3000);
myServer.signalAll_B();
}
}

在上述代码中我们定义了两个Condition,分别用于两个线程的等待和唤醒,而我们只唤醒线程B而线程A则继续等待。结果如下:

1
2
3
4
begin await A 时间为 1551424032113
begin await B 时间为 1551424032116
signalall B 时间为 1551424035121
end await B 时间为 1551424035122

公平锁与非公平锁

公平锁就是线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出的顺序。非公平锁就是随机获取锁的,属于一种枪占机制。先来不一定先得到,谁抢到属于谁,所以是不公平的。

通过构造方法ReetrantLock(boolean isFair)来创建对应公平锁或不公平锁。无参默认为非公平锁。

使用ReentrantReadWriteLock类

使用ReetrantLock具有完全互斥排他的效果,即同一时间只有一个线程能够执行Reentrantlock.lock()方法后的代码。虽然这样能保持实例变量的线程安全性,但是效率确实非常低的。然而使用读写锁ReentrantReadWriteLock类,可以提高运行效率,在不需要操作实例变量的情况下,可以使用读写锁ReentrantReadWriteLock来提高任务的执行效率。

读写锁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
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
public class MyServer {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public void read() {
try {
lock.readLock().lock();
System.out.println("获得读锁:"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}

public void write() {
try {
lock.writeLock().lock();
System.out.println("获得写锁:"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}

public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();
Thread threadA = new Thread(new Runnable() {
public void run() {
myServer.read();
}
});
threadA.setName("A");
threadA.start();

Thread threadB = new Thread(new Runnable() {
public void run() {
myServer.write();
}
});
threadB.setName("B");
threadB.start();

Thread threadC = new Thread(new Runnable() {
public void run() {
myServer.read();
}
});
threadC.setName("C");
threadC.start();
}
}

结果如下:

1
2
3
获得读锁:A 1551509003778
获得读锁:A 1551509013785
获得读锁:A 1551509023789

由结果可知,读写和写读是互斥的

读读共享

更改上面的main方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();

Thread threadA = new Thread(new Runnable() {
public void run() {
myServer.read();
}
});
threadA.setName("A");
threadA.start();

Thread threadB = new Thread(new Runnable() {
public void run() {
myServer.read();
}
});
threadB.setName("B");
threadB.start();
}

结果如下:

1
2
获得读锁:A 1551509377678
获得读锁:B 1551509377678

由结果可知:读和读共享

写写互斥

更改上面的main方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();

Thread threadA = new Thread(new Runnable() {
public void run() {
myServer.write();
}
});
threadA.setName("A");
threadA.start();

Thread threadB = new Thread(new Runnable() {
public void run() {
myServer.write();
}
});
threadB.setName("B");
threadB.start();
}

结果如下:

1
2
获得写锁:A 1551509613785
获得写锁:B 1551509623788

由结果知:写写互斥

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :