多线程
5 Lock显式锁
在 JDK5 中增加了Lock锁接口, 有 ReentrantLock 实现类,ReentrantLock 锁称为可重入锁, 它的功能比 synchronized多.
5.1 锁的可重入性
锁的可重入性是指,当一个线程获得一个对象锁后,再次请求该对象锁时是可以获得该对象的锁的.
5.2 ReentrantLock
5.2.1 ReentrantLock的基本使用
调用 lock()方法获得锁, 调用 unlock()释放锁。
package com.yupeng.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* lock锁的基本使用
* @author Yupeng
* @create 2020-12-09 21:39
*/
public class Test01 {
public static void main(String[] args) {
// 创建三个线程
new SubThread().start();
new SubThread().start();
new SubThread().start();
}
// 定义显式锁
static Lock lock = new ReentrantLock();
// 定义方法
public static void sm() {
//先获得锁
lock.lock();
try {
// 同步代码块
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
} finally {
// 释放锁
lock.unlock();
}
}
// 定义线程类
static class SubThread extends Thread{
@Override
public void run() {
sm();
}
}
}
使用lock锁,可以同步不同方法中的同步代码块。
5.2.2 ReentrantLock的可重入性
package com.yupeng.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock的可重入性
* @author Yupeng
* @create 2020-12-09 22:02
*/
public class Test02 {
public static void main(String[] args) throws InterruptedException {
SubThread t1 = new SubThread();
SubThread t2 = new SubThread();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(SubThread.num);
}
static class SubThread extends Thread{
private static Lock lock = new ReentrantLock(); // 定义锁对象
public static int num = 0;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
lock.lock();
try {
// 可重入性指的是可以反复获得该锁
lock.lock();
try {
num++;
}finally {
lock.unlock();
}
}finally {
lock.unlock();
}
}
}
}
}
5.2.3 lockInterruptibly()方法
lockInterruptibly() 方法的作用:在等待获得锁的过程中,如果当前线程未被中断则可以正常获得锁;如果在等待获得锁的过程中,当前线程被中断,则出现异常。
package com.yupeng.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* lockInterruptibly() 方法的作用:在等待获得锁的过程中,如果当前线程未被中断则可以正常获得锁;如果在等待获得锁的过程中,当前线程被中断,则出现异常。
* @author Yupeng
* @create 2020-12-10 16:18
*/
public class Test03 {
public static void main(String[] args) {
Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
service.sm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
// 保证线程t1先执行
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
// 睡眠时间不能过长,保证结束睡眠后线程t1还在执行
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();
}
static class Service{
// 定义锁对象
private ReentrantLock lock = new ReentrantLock();
public void sm() throws InterruptedException {
// lock.lock(); //获得锁对象,即使调用了线程的interrupt()方法,也没有真正的中断线程
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + "获得锁对象...");
System.out.println(Thread.currentThread().getName() + "开始同步...");
// 执行一段耗时操作
for (int i = 0; i < 1000000000; i++) {
new StringBuilder();
}
System.out.println( Thread.currentThread().getName() + "结束同步!");
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
System.out.println(Thread.currentThread().getName() + "释放锁对象!");
}
}
}
}
对于 synchronized 内部锁来说,如果一个线程在等待锁,只有两个结果:要么该线程获得锁继续执行;要么就保持等待.
对于 ReentrantLock 可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求. 比如通过 ReentrantLock 锁的 lockInterruptibly()方法避免死锁的产生。
package com.yupeng.lock.reentrant;
import java.util.concurrent.locks.ReentrantLock;
/**
* 通过 ReentrantLock锁的lockInterruptibly()方法避免死锁的产生
* @author Yupeng
* @create 2020-12-10 16:44
*/
public class Test04 {
public static void main(String[] args) throws InterruptedException {
IntLock intLock1 = new IntLock(5); // 奇数
IntLock intLock2 = new IntLock(6); // 偶数
Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start();
t2.start();
// main线程睡眠3秒,如果线程没有结束,就中断线程
Thread.sleep(3000);
// 可以中断任何一个线程来解决死锁
if (t1.isAlive()) {
t1.interrupt();
}
}
static class IntLock implements Runnable{
// 创建两个锁对象
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
// 定义整数变量,决定使用哪个锁
int lockNum;
public IntLock(int lockNum) {
this.lockNum = lockNum;
}
@Override
public void run() {
if (lockNum % 2 == 1){ // 奇数,先获得锁1,再获得锁2
try {
lock1.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName() + "获得锁1,还需要继续获得锁2");
lock2.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + "获得了锁1和锁2,可以继续执行...");
} finally {
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁2");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁1");
}
}
} else { // 偶数,先获得锁2,再获得锁1
try {
lock2.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName() + "获得锁2,还需要继续获得锁1");
lock1.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + "获得了锁1和锁2,可以继续执行...");
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁1");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁2");
}
}
}
}
}
}
// 运行结果:
// t1线程获得了锁1,t2线程获得了锁2,发生死锁,t1线程在申请锁2的过程中被中断,则放弃对锁2的申请并且释放持有的锁1,使得t2获得锁1,继续执行。
Thread-0获得锁1,还需要继续获得锁2
Thread-1获得锁2,还需要继续获得锁1
java.lang.InterruptedException
...
Thread-0释放锁1
Thread-1获得了锁1和锁2,可以继续执行...
Thread-1释放锁1
Thread-1释放锁2
5.2.4 tryLock()方法
tryLock(long time, TimeUnit unit)方法的作用是:在给定等待时长内,如果锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁.通过该方法可以实现锁对象的限时等待.
package com.yupeng.lock.reentrant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* tryLock(long time, TimeUnit unit)的基本使用
* @author Yupeng
* @create 2020-12-10 17:20
*/
public class Test05 {
public static void main(String[] args) {
TimeLock timeLock = new TimeLock();
Thread t1 = new Thread(timeLock);
Thread t2 = new Thread(timeLock);
t1.start();
t2.start();
}
static class TimeLock implements Runnable{
// 定义锁对象
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if (lock.tryLock(3, TimeUnit.SECONDS)){ // 在给定等待时长内,如果锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁
System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务");
// Thread.sleep(4000); // 执行任务需要4秒,已经超过等待时长,其他线程将放弃申请锁
Thread.sleep(2000); // 执行任务需要2秒,没有超过等待时长,其他线程可以获得锁
} else{
System.out.println(Thread.currentThread().getName() + "没有获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
}
}
}
tryLock()方法,调用方法尝试获得锁,如果该锁没有被其他线程占用,则返回 true,锁定成功; 如果锁被其他线程占用,则返回 false,不等待.
package com.yupeng.lock.reentrant;
import java.util.concurrent.locks.ReentrantLock;
/**
* tryLock()方法的使用
* @author Yupeng
* @create 2020-12-10 17:35
*/
public class Test06 {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
service.serviceMethod();
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
// 保证t1先执行
Thread.sleep(500);
t2.start();
}
static class Service{
// 定义锁对象
private ReentrantLock lock = new ReentrantLock();
public void serviceMethod() {
try {
if (lock.tryLock()) {
System.out.println(Thread.currentThread().getName() + "获得锁,继续执行...");
Thread.sleep(3000); // 模拟任务执行
}else {
System.out.println(Thread.currentThread().getName() + "没有获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
}
}
}
// 运行结果:
Thread-0获得锁,继续执行...
Thread-1没有获得锁
Thread-0释放锁
5.2.5 newCondition()方法
内部锁synchronized 与 wait()/notify()这两个方法一起使用,可以实现等待/通知机制. Lock 锁的 newContition()方法返回 Condition 对
象,Condition 类也可以实现等待/通知模式.
使用 notify()通知时, JVM 会随机唤醒某个等待的线程. 使用Condition 类可以进行选择性通知. Condition 比较常用的两个方法:
await()
使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行.
signal()
用于唤醒一个等待的线程
注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关的 Lock 锁. 调用 await()后线程会释放这个锁,在 singal()调用后会从当前 Condition 对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行.
基本使用:
package com.yupeng.lock.reentrant;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition的基本使用
* @author Yupeng
* @create 2020-12-10 18:20
*/
public class Test07 {
public static void main(String[] args) throws InterruptedException {
SubThread t1 = new SubThread();
t1.start();
Thread.sleep(2000);
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
// 定义锁对象
static private Lock lock = new ReentrantLock();
// 定义Condition对象
static Condition condition = lock.newCondition();
// 定义线程子类
static class SubThread extends Thread{
@Override
public void run() {
lock.lock(); // 调用await()之前,必须获得锁
try {
System.out.println("获得锁对象");
condition.await();
System.out.println("开始等待...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("释放锁对象");
}
}
}
}
多个Condition 实现通知部分线程, 使用更灵活:
package com.yupeng.lock.reentrant;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 多个 Condition 实现通知部分线程, 使用更灵活
* @author Yupeng
* @create 2020-12-10 18:30
*/
public class Test08 {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
new Thread(new Runnable() {
@Override
public void run() {
service.waitMethodA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
service.waitMethodB();
}
}).start();
// main线程睡眠3秒
Thread.sleep(3000);
service.signalMethodA(); // 唤醒conditionA对象上的等待,conditionB对象上的等待继续等待
}
static class Service{
// 定义锁对象
private ReentrantLock lock = new ReentrantLock();
// 定义两个condition对象
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
// 定义方法,使用conditionA等待
public void waitMethodA() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " begin await:" +System.currentTimeMillis() );
conditionA.await();
System.out.println(Thread.currentThread().getName() + " end await:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 定义方法,使用conditionB等待
public void waitMethodB() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " begin await:" +System.currentTimeMillis() );
conditionB.await();
System.out.println(Thread.currentThread().getName() + " end await:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 定义方法,唤醒conditionA对象上的等待
public void signalMethodA() {
lock.lock();
try {
conditionA.signal();
System.out.println(Thread.currentThread().getName() + " signal A:" + System.currentTimeMillis());
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 定义方法,唤醒conditionB对象上的等待
public void signalMethodB() {
lock.lock();
try {
conditionB.signal();
System.out.println(Thread.currentThread().getName() + "signal B:" + System.currentTimeMillis());
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
}
使用condition实现两个线程交替打印:
package com.yupeng.lock.reentrant;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用condition实现两个线程交替打印
* @author Yupeng
* @create 2020-12-10 22:05
*/
public class Test09 {
public static void main(String[] args) {
Service service = new Service();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
service.printOne();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
service.printTwo();
}
}
}).start();
}
static class Service{
// 定义锁对象
private ReentrantLock lock = new ReentrantLock();
// 定义condition对象
private Condition condition = lock.newCondition();
// 定义交替打印标志
private boolean flag = true;
// 定义方法,打印数字1
public void printOne() {
lock.lock();
try {
while (!flag) {
condition.await();
}
System.out.println(Thread.currentThread().getName() + "打印1");
flag = false;
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
// 定义方法,打印数字2
public void printTwo() {
lock.lock();
try {
while (flag) {
condition.await();
}
System.out.println(Thread.currentThread().getName() + "打印2");
flag = true;
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
}
5.2.6 公平锁与非公平锁
大多数情况下,锁的申请都是非公平的. 如果线程1与线程2都在请求锁 A, 当锁 A 可用时, 系统只是会从阻塞队列中随机的选择一个线程,不能保证其公平性.
公平的锁会按照时间先后顺序,保证先到先得, 这一特点使得不会出现线程饥饿现象.
synchronized 内部锁是非公平的. ReentrantLock 重入锁提供了一个构造方法:ReentrantLock(boolean fair) ,创建锁对象时实参传递true ,可以把该锁设置为公平锁.
公平锁看起来很公平,但是要实现公平锁必须要求系统维护一个有序队列,公平锁的实现成本较高,性能也低. 因此默认情况下锁是非公平的. 除非是特别的需求,一般不使用公平锁.
package com.yupeng.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁与非公平锁
* @author Yupeng
* @create 2020-12-10 22:44
*/
public class Test01 {
// 默认是非公平锁
// static ReentrantLock lock = new ReentrantLock();
static ReentrantLock lock = new ReentrantLock(true);
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获得了锁对象");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
}
}
/*
1)如果是非公平锁, 系统倾向于让一个线程再次获得已经持有的锁, 这种分配策略是高效的,非公平的
2)如果是公平锁, 多个线程不会发生同一个线程连续多次获得锁的可能,保证了公平性
*/
5.2.7 常用方法
int getHoldCount() //返回当前线程调用lock()方法的次数
int getQueueLength() //返回正在等待获得锁的线程预估数
boolean hasWaiters (Condition condition) // 查询是否有线程正在等待指定的 Condition 条件
int getWaitQueueLength (Condition condition) // 返回与Condition条件相关的等待的线程预估数
boolean hasQueuedThreads() // 查询是否还有线程在等待获得该锁
boolean hasQueuedThread(Thread thread) // 查询参数指定的线程是否在等待获得该锁
boolean isFair() // 判断是否为公平锁
boolean isLocked() // 查询当前锁是否被持有
boolean isHeldByCurrentThread() // 判断该锁是否被当前线程持有
5.3 ReentrantReadWriteLock 读写锁
synchronized 内部锁与 ReentrantLock 锁都是独占锁(排它锁), 同一时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低.
ReentrantReadWriteLock 读写锁是一种改进的排他锁,也可以称作共享/排他锁. 允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新.
读写锁通过读锁与写锁来完成读写操作. 线程在读取共享数据前必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的.线程在修改共享数据前必须先持有写锁,写锁是排他的, 一个线程持有写锁时其他线程无法获得相应的锁。
读锁只是在读线程之间共享,任何一个线程持有读锁时,其他线程都无法获得写锁, 保证线程在读取数据期间没有其他线程对数据进行更新,使得读线程能够读到数据的最新值,保证在读数据期间共享变量不被修改。
获得条件 | 排他性 | 作用 | |
---|---|---|---|
读锁 | 写锁未被任意线程持有 | 对读线程是共享的, 对写线程是排他的 | 允许多个读线程可以同时读 取共享数据,保证在读共享数 据时,没有其他线程对共享数 据进行修改 |
写锁 | 该写锁未被其他线程持 有,并且相应的读锁也未 被其他线程持有 | 对读线程或者写线程 都是排他的 | 保证写线程以独占的方式修 改共享数据 |
概括起来就是:读写锁允许读读共享, 读写互斥,写写互斥。
在java.util.concurrent.locks包中定义了ReadWriteLock接口,该接口中定义了 readLock()方法返回读锁,定义 writeLock()方法返回写锁. 该接口的实现类是 ReentrantReadWriteLock.
注意:readLock()与 writeLock()方法返回的锁对象是同一个锁的两个不同的角色, 不是分别获得两个不同的锁. ReadWriteLock 接口实例可以充当两个角色.
读写锁的基本使用方法:
// 定义读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 获得读锁
Lock readLock = rwLock.readLock();
// 获得写锁
Lock writeLock = rwLock.writeLock();
// 读数据
readLock.lock(); // 申请读锁
try{
// 读取共享数据
}finally{
readLock.unlock(); // 总是在finally子句中释放锁
}
// 写数据
writeLock.lock(); // 申请写锁
try{
// 更新修改共享数据
}finally{
writeLock.unlock(); // 总是在finally子句中释放锁
}
5.3.1 读读共享
ReadWriteLock 读写锁可以实现多个线程同时读取共享数据,即读读共享,可以提高程序的读取数据的效率。
package com.yupeng.lock.readwrite;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock读写锁可以实现读读共享,允许多个线程同时获得读锁
* @author Yupeng
* @create 2020-12-11 9:57
*/
public class Test01 {
public static void main(String[] args) {
Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
service.read();
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
}
static class Service{
// 定义读写锁对象
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void read() {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取数据...(读取时间:" + System.currentTimeMillis() +")");
Thread.sleep(2000); // 模拟读取数据的耗时
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
}
// 运行程序后,多个线程几乎可以同时获得锁读,执行lock()后面的代码
Thread-1获得读锁,开始读取数据...(读取时间:1607652206408)
Thread-0获得读锁,开始读取数据...(读取时间:1607652206409)
Thread-4获得读锁,开始读取数据...(读取时间:1607652206409)
Thread-2获得读锁,开始读取数据...(读取时间:1607652206409)
Thread-3获得读锁,开始读取数据...(读取时间:1607652206409)
5.3.2 写写互斥
通过 ReadWriteLock 读写锁中的写锁,只允许有一个线程执行 lock()后面的代码.
package com.yupeng.lock.readwrite;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock的writeLock()写锁是互斥的,只允许有一个线程持有
* @author Yupeng
* @create 2020-12-11 10:06
*/
public class Test02 {
public static void main(String[] args) {
Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
service.write();
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
}
static class Service{
// 定义锁对象
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 定义写数据的方法
public void write() {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入数据...(写入时间:" + System.currentTimeMillis() + ")");
Thread.sleep(2000); // 模拟写入数据的耗时
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "写入时间完毕!(完成时间:" + System.currentTimeMillis() + ")");
readWriteLock.writeLock().unlock();
}
}
}
}
// 从执行结果来看,同一时间只有一个线程获得写锁
Thread-0开始写入数据...(写入时间:1607652962175)
Thread-0写入时间完毕!(完成时间:1607652964176)
Thread-1开始写入数据...(写入时间:1607652964176)
Thread-1写入时间完毕!(完成时间:1607652966176)
Thread-2开始写入数据...(写入时间:1607652966176)
Thread-2写入时间完毕!(完成时间:1607652968177)
Thread-3开始写入数据...(写入时间:1607652968177)
Thread-3写入时间完毕!(完成时间:1607652970177)
Thread-4开始写入数据...(写入时间:1607652970177)
Thread-4写入时间完毕!(完成时间:1607652972178)
5.3.3 读写互斥
写锁是独占锁,是排他锁,读线程与写线程也是互斥的。
package com.yupeng.lock.readwrite;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写互斥:一个线程获得读锁时,写线程等待; 一个线程获得写锁时,其他线程等待
* @author Yupeng
* @create 2020-12-11 10:18
*/
public class Test03 {
public static void main(String[] args) {
Service service = new Service();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
service.read();
}
};
Runnable writeRunnable = new Runnable() {
@Override
public void run() {
service.write();
}
};
// 创建两个写线程和两个读线程
new Thread(writeRunnable).start();
new Thread(writeRunnable).start();
new Thread(readRunnable).start();
new Thread(readRunnable).start();
}
static class Service{
// 定义锁对象
private ReadWriteLock lock = new ReentrantReadWriteLock();
private Lock readLock = lock.readLock();
private Lock writeLock = lock.writeLock();
public void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取数据...(读取时间:" + System.currentTimeMillis() +")");
Thread.sleep(2000); // 模拟读取数据的耗时
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
public void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入数据...(写入时间:" + System.currentTimeMillis() + ")");
Thread.sleep(2000); // 模拟写入数据的耗时
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "写入时间完毕!(完成时间:" + System.currentTimeMillis() + ")");
writeLock.unlock();
}
}
}
}