java高级多线程 之 Lock
关于锁的概念,需要读者首先对线程安全、死锁有一定的了解,在此前提下会对本篇文章有更加深入的了解,并能够很好的与
synchronized
比较,进而灵活的使用;因此,附上几篇链接,望对您有帮助!
锁 Lock
简述
JDK5加入,与synchronized
比较,显示定义,结构更灵活;
synchronized
方法中锁的获取和释放都是不可预见的,而Lock
是通过调用方法实现的,是显性的;- Lock实现提供比使用
synchronized
方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition
。
方法
方法 | 描述 |
---|---|
void lock() | 获得锁。 |
void lockInterruptibly() | 获取锁定,除非当前线程是 interrupted 。 |
Condition newCondition() | 返回一个新Condition绑定到该实例Lock实例。 |
boolean tryLock() | 只有在调用时锁为空闲状态才可以获得锁。 |
boolean tryLock(long time, TimeUnit unit) | 如果在给定的等待时间内是空闲的,并且当前的线程尚未得到 而被中断,则获取该锁。 |
void unlock() | 释放锁。 |
相比之前而言,这里有一个比较有趣的方法就是tryLock()
;在没使用这个方法之前,通常是下面的情形:
一般而言,对于线程来说,当访问临界资源对象而未拿到锁标记时,会进入阻塞状态,一直等待该锁标记释放为止;
而对于tryLock()
而言,在线程访问临界资源对象时会尝试去拿锁标记,拿到锁标记返回ture
,并完整整个线程的执行;而拿不到锁标记返回false
,并不进入阻塞状态,线程可以选择去做其他事情,过一个周期或几个周期再来访问,直到临界资源的时间片被释放为止;
这个可以类比海底捞排号,你去海底捞吃饭,很多时候都需要排队,而排队并不是传统的排队,而是按照先来后到进行排号,用户拿到排号不必一直原地等待,可以在此期间去购物啊等做其他事情;
重入锁 ReentrantLock
简述
**ReentrantLock:**Lock接口实现类,一个可重入互斥锁具有与使用synchronized
方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。
使用:
详戳:深入理解Java线程安全——银行欠我400万!!!
之前在此链接的代码案例中,没处理之前线程是不安全的,而当时的做法时通过对银行这个临界资源对象加锁(使用synchronized)解决,同样地,使用Lock
来解决线程安全问题。格式如下:
- 使用
synchronized
:
synchronized (临界资源对象){ //对临界资源加锁
//代码(原子操作)
}
- 使用
lock
:
//创建一个重入锁对象
Lock locker = new ReentrantLock();
//开启锁
locker.lock();
//代码(原子操作)
//释放锁
locker.unlock();
若代码中可能抛异常,为了是程序不因抛异常而导致释放锁无法,因此会用到try finally
结构,使得程序再抛异常之前先把锁资源释放掉,这也是finally
的特点和优势;
//创建一个重入锁对象
Lock locker = new ReentrantLock();
locker.lock();
try{
//可能出现异常的代码
}finally{
locker.unlock();
//无论是否出现异常,都需要 执行的代码结构,用于释放锁资源
}
读写锁 ReentrantReadWriteLock
简述
A ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。 read lock可以由多个阅读器线程同时进行,只要没有writer。 write lock是独占的。
ReentrantReadWriteLock:
- 一种支持一写多读的同步锁。读写分离。可分别分配读锁、写锁。
- 支持多次分配读锁。使多个读操作可以并发执行。
读写锁与互斥锁区别
显然,读写锁有多把锁,一把锁用于分配给读线程,另一把分配给写线程,为什么要这样呢?原因在与读操作并不会改变数据,在一个读操作大于写操作的场合下,由于读操作并不会改变数据,多个线程并发执行并不会导致数据的不一致,因此读锁就不会产生互斥;而写操作会改变数据,因此写锁是互斥的;如此细分,读写锁在读操作大于写操作的环境中,在保证线程安全的情况下,读写锁效率远远高于互斥锁。
读写互斥规则
- 写-写:互斥,阻塞;
- 读-写:互斥,读阻塞写、写阻塞读
- 读-读:不互斥、不阻塞
方法
方法 | 描述 |
---|---|
Lock readLock() | 返回用于阅读的锁。 |
Lock writeLock() | 返回用于写入的锁。 |
读写锁代码案例
模拟读写操作
新建一个Dog类,该对象有一个属性时value,创建setter、getter方法表示读写操作;使用读写锁,首先要创建读写锁,然后从ReentrantReadWriteLock
分别拿出写锁和读锁,在set和get方法中使用,每个锁的使用都必须有开启锁和释放锁的两个步骤,且释放锁资源必须放在finally
中,以保证程序的执行后释放锁资源;
在使用读写锁加入Thread.sleep(1000);
是线程执行睡眠,是因为线程执行的速度是很快的,依次减慢线程的执行速度(设定线程执行为1s),更容易看出其执行效率;
class Dog{
String value;
//创建读写锁
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//内部类对象
ReadLock r1 = rwl.readLock();
WriteLock w1 = rwl.writeLock();
//读
public String getValue() {
r1.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
} finally {
r1.unlock();
}
}
//写
public void setValue(String value) {
w1.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
} finally {
w1.unlock();
}
}
}
创建读写任务
手首先通过Callable接口创建两个任务即readTask
、writeTask
,然后创建一个线程池,随即记录当前系统时间,然后依次分别循环执行读写任务,这里读任务是18次,写任务是2次;执行完毕后完毕当前线程池并记录当前系统时间,打印开始和结束时间,即得到执行时间,通过执行时间的快慢来衡量效率;
public class TestReentrantReadWriteLock {
public static void main(String[] args){
final Dog dog = new Dog();
Callable<Object> writeTask = new Callable<Object>() {
@Override
public Object call() throws Exception {
dog.setValue("hello");
return null;
}
};
Callable<Object> readTask = new Callable<Object>() {
@Override
public Object call() throws Exception {
dog.getValue();
return null;
}
};
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(20);
//开始时间
long start = System.currentTimeMillis();
//写操作提交3次
for (int i = 0; i <2 ; i++) {
executorService.submit(writeTask);
}
//读操作提交19次
for (int i = 0; i <18; i++) {
executorService.submit(readTask);
}
//停止线程池,(不再接受新任务,将现有的任务全部执行完毕)
executorService.shutdown();
while(true){
System.out.println("结束了吗?");
if(executorService.isTerminated())
break;
}
//结束时间
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
执行结果
总结分析
程序的执行时间是4s左右,说明了什么?
- 这说明对于读写操作来说,写锁是互斥的,不能并发执行,而读锁不是互斥的可以并发执行,因此对于互斥锁来说,不存在读写之别,因为互斥,所以都不能并发,相比于此,读写锁能够大大提高效率。