显式锁·读写锁·条件阻塞
在一些多个线程同时访问一些共享数据,一般都会涉及到线程安全问题。这时候就需要使用一些手段去防止多个线程同时去修改共享数据,防止同时去执行某段代码逻辑。这时候就要考虑到加锁了。
0 锁的条件
具有排他性,同一时刻只能有一个对象获取到锁。
1. 显式锁(Lock)
1.1 用法场景
- 多个线程对同一个共享资源进行操作。
- 防止重复请求。
1.2 Lock与synchronized
synchronized:java内置锁,一般会用于锁一些代码块,不需要手动的去释放锁,一般与Object类中的wait()和notify()配合使用。
Lock:显示锁,一般用于锁一个对象,需要手动的去释放锁,要不然会一直被锁。
目前微服务和分布式盛行的时代,Lock用的都很少了,一般都会使用第三方工具来实现锁。最常用的是Redis的锁。
jdk也对Lock进行了实现ReentrantLock。ReentrantLock和synchronized都是可重入锁,一个线程在锁定的代码块里继续调用该锁锁定的方法或代码块,是能进入锁定的代码中运行的。类似于下面的代码。
public void useLock() {
lock.lock(); //阻塞方法,一直在等待获取锁
try {
System.out.println("Thread:" + Thread.currentThread().getName() + "运行。。。");
Thread.sleep(3000);
useLock();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
1.3 显式锁(Lock)的具体实现
Lock有三种获取方式:
1.lock():阻塞方法,一直在等待获取锁,知道获取成功。
2.tryLock():尝试着去获取锁,获取不到返回false,获取到返回true。
3.lock.tryLock(1, TimeUnit.SECONDS):尝试获取锁,如果获取设置的等待时间内还是获取不到锁,返回false,获取到返回true。
最后不要忘记在finally中主动释放锁。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程锁的使用
*/
public class UseLock {
Lock lock = new ReentrantLock(); // 可重入锁
public void useLock() {
lock.lock(); //阻塞方法,一直在等待获取锁
try {
System.out.println("Thread:" + Thread.currentThread().getName() + "运行。。。");
Thread.sleep(3000);
useLock();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void useTryLock() {
if (lock.tryLock()) { //尝试获取锁,获取不到返回false
try {
System.out.println("Thread:" + Thread.currentThread().getName() + "运行。。。");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
System.out.println("Thread:" + Thread.currentThread().getName() +"线程锁获取失败。");
}
}
public void useTryLockTimeout() throws InterruptedException {
if (lock.tryLock(1, TimeUnit.SECONDS)) { //尝试获取锁,等待一段时间后还是获取不到再返回false
try {
System.out.println("Thread:" + Thread.currentThread().getName() + "运行。。。");
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
System.out.println("Thread:" + Thread.currentThread().getName() +"线程锁获取失败。");
}
}
public static void main(String[] args) {
UseLock useLock = new UseLock();
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("useLock1");
useLock.useLock();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("useLock2");
useLock.useLock();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("useLock3");
useLock.useLock();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("useTryLock1");
useLock.useTryLock();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("useTryLock2");
useLock.useTryLock();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.currentThread().setName("useTryLockTimeout1");
useLock.useTryLockTimeout();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.currentThread().setName("useTryLockTimeout2");
useLock.useTryLockTimeout();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
2. 读写锁(ReadWriteLock)
2.1用法场景
1.一般用于读多写少的场景。
2.2原理
当线程获取到写锁时,别的线程读锁和写锁都获取不到。当线程获取到读锁时,别的线程也能正常获取到读锁和写锁。具体底层实现有待研究。
2.3实现代码
ReadWriteLock接口由ReentrantReadWriteLock实现。ReentrantReadWriteLock中有两个内部类ReadLock和WriteLock,这两个类都实现Lock接口。
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁的使用
*/
public class UseReadWriteLock {
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();
Lock writeLock = lock.writeLock();
int num = 0;
public void setNum(int num) {
writeLock.lock();
try {
this.num = num;
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
public int getNum() {
readLock.lock();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return this.num;
}
public static void main(String[] args) throws InterruptedException {
UseReadWriteLock useReadWriteLock = new UseReadWriteLock();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
long startTime = System.currentTimeMillis();
int num = new Random().nextInt();
useReadWriteLock.setNum(num);
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "写" + num + "耗时" + (endTime - startTime));
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
long startTime = System.currentTimeMillis();
int num = useReadWriteLock.getNum();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "读取num耗时" + (endTime - startTime) + "ms,读取的值为" + num);
}
}).start();
}
}
}
3. 条件阻塞(Condition)
3.1 Condition的优点
和Object中的三个方法相比,Condition的优点是:
1.Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的。
2.Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,“读线程"需要等待。 如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程”,而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。
在使用Condition时,必须保证当前对象已经获取到锁,不然会报错。
3.2具体实现
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 条件阻塞Condition
*/
public class UseCondition {
static Lock lock = new ReentrantLock(); // 可重入锁
static Condition packCondition = lock.newCondition(); //包装
static Condition workCondition = lock.newCondition(); //生产
static Condition repairCondition = lock.newCondition(); //维修
static Boolean finish = false;
//包装类
static class Pack implements Runnable {
@Override
public void run() {
lock.lock();
try {
int i = 1;
do {
System.out.println("包装部门等待包装");
packCondition.await();
Thread.sleep(2000);
System.out.println("包装部门包装好第" + i + "包");
workCondition.signal();
i++;
} while (i <= 5);
finish = true;
repairCondition.signal();
System.out.println("包装部门工作完成");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
//生产部门
static class Work implements Runnable {
@Override
public void run() {
lock.lock();
try {
int i=0;
do {
System.out.println("生产部门开始生产");
Thread.sleep(2000);
Random random = new Random();
int num = random.nextInt(10);
System.out.println("随机数是"+num);
if (num % 4 != 0) {
//生产部门完成工作,通知包装部门
System.out.println("生产部门生产完成,通知包装部门包装,等待包装部回馈");
packCondition.signal();
workCondition.await();
System.out.println("生产部门收到包装部门完成回馈,检查是否完成");
i++;
} else {
//生产部门通知维修部门修机器
System.out.println("机器坏了,生产部门通知维修部门修机器,并等待修好");
repairCondition.signal();
workCondition.await();
System.out.println("机器修好,开始工作。");
}
} while (!finish);
System.out.println("生产部门完成了今天的工作,共生产了"+i+"包");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//维修部门
static class Repair implements Runnable {
@Override
public void run() {
lock.lock();
try {
do {
System.out.println("维修部门待命");
repairCondition.await();
if(finish){
System.out.println("工作完成,维修部门结束工作");
break;
}
System.out.println("维修部门接到通知开始修机器");
Thread.sleep(2000);
System.out.println("维修部门修好机器通知生产部");
workCondition.signal();
} while (true);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Pack()).start();
new Thread(new Repair()).start();
new Thread(new Work()).start();
}
}