java中Lock锁的应用简介
整体描述
说到java,就不能不提多线程和锁,这篇文章简单介绍一下java中Lock的几种常用的应用方式。先简单介绍一下锁。锁,顾名思义,就是可以把东西锁住,在java里,可以理解为把一段代码锁住,只能让一个线程访问并执行,其他线程想再执行这段代码,就要等前面的线程结束才可以。锁的应用场景有很多,具体可以用于:读写数据操作,请求第三方接口时要求不能并发请求等。
方法介绍
lock是java自带的类,包括几个方法,下面具体介绍一下。
1. void lock()
获取锁。如果没有线程再使用这个锁,即锁可用,则能正常获取到这个锁,就可以正常往下执行,如果其他线程在使用这个锁,即获取锁失败,就会在这里等待,直到其他线程使用完释放这个锁,才能继续执行。
2. boolean tryLock()
获得锁。和上面的方法的区别是,这个方法获取锁不会等待,如果锁可用立即返回true,如果锁不可用立即返回false;即不管锁可用不可用,都会立即返回锁的状态。
3. boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
获得锁。和上面的方法的区别是,如果锁可用,立即返回true,否则会等待,等待时间期间锁变为可用状态,立即返回true,到达等待时间,返回false。
4. void unlock()
释放锁。使用之后,必须手动释放锁,否则其他线程就永远无法访问了。所以释放锁的操作要放在finally块中进行,以保证锁一定会被释放,防止死锁的发生。
代码演示
先介绍了最简单的Lock,这里写几段代码Demo,根据执行结果,更加好理解锁的原理。
1. 基础代码
先写一个Test类,用于测试
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static final String FULL_TIME_PATTERN_ALL = "yyyy-MM-dd HH:mm:ss:SSS";
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
for (int i = 1; i < 4; i++) {
Integer finalI = i;
new Thread(() -> {
LockTest(finalI);
}, finalI.toString()).start();
}
}
public static String getDateFormat(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FULL_TIME_PATTERN_ALL, Locale.CHINA);
return simpleDateFormat.format(date);
}
}
1. lock()
public static void LockTest(Integer id) {
System.out.println(getDateFormat(new Date()) + "-准备执行:" + id);
lock.lock();
try {
System.out.println(getDateFormat(new Date()) + "-开始执行:" + id);
Thread.sleep(2000L);
} catch (Exception e) {
System.out.println(getDateFormat(new Date()) + "-执行出错:" + e.toString());
} finally {
lock.unlock();
System.out.println(getDateFormat(new Date()) + "-成功执行:" + id);
}
}
运行结果:
根据打印的时间可以看出,三个线程同时调用锁,先是线程1获得了锁,并执行,2秒之后执行完成,线程1释放锁,之后线程2获得了锁,然后是线程3,和我们预想的一样。
2. boolean tryLock()
public static void LockTest(Integer id) {
System.out.println(getDateFormat(new Date()) + "-准备执行:" + id);
boolean flag = lock.tryLock();
if (flag) {
try {
System.out.println(getDateFormat(new Date()) + "-开始执行:" + id);
Thread.sleep(2000L);
} catch (Exception e) {
System.out.println(getDateFormat(new Date()) + "-执行出错:" + e.toString());
} finally {
lock.unlock();
System.out.println(getDateFormat(new Date()) + "-成功执行:" + id);
}
} else {
System.out.println(getDateFormat(new Date()) + "-成功失败:" + id);
}
}
运行结果:
根据打印的时间可以看出,三个线程同时调用锁,先是线程1获得了锁,并执行,线程2和线程3都是立即返回获取锁失败,都执行失败了,线程1两秒之后执行完成,和我们预想的一样。
3. boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
public static void LockTest(Integer id) {
System.out.println(getDateFormat(new Date()) + "-准备执行:" + id);
boolean flag = false;
try {
flag = lock.tryLock(3000, TimeUnit.MILLISECONDS);
} catch (Exception e) {
System.out.println(getDateFormat(new Date()) + "-执行出错:" + e.toString());
}
if (flag) {
try {
System.out.println(getDateFormat(new Date()) + "-开始执行:" + id);
Thread.sleep(2000L);
} catch (Exception e) {
System.out.println(getDateFormat(new Date()) + "-执行出错:" + e.toString());
} finally {
lock.unlock();
System.out.println(getDateFormat(new Date()) + "-成功执行:" + id);
}
} else {
System.out.println(getDateFormat(new Date()) + "-执行失败:" + id);
}
}
运行结果:
根据打印的时间可以看出,三个线程同时调用锁,先是线程1获得了锁,并执行,其他两个线程获取锁失败,但是由于我们设置了等待时间是3秒,等过两秒之后线程1执行完成,线程2开始执行,等过三秒之后,由于到达等待时间,线程3执行失败,之后线程2执行完成,和我们预想的一样。
简单总结一下,这三种方式各有各的用处,在使用的时候,可以按需选择。下面再介绍一个ReadWriteLock。
读写锁;
ReadWriteLock。用于读写文件操作,定义的方法为readLock()和writeLock()。一个用来获取读锁,一个用来获取写锁。由于读写操作的特殊性,度操作可以多线程同时访问,但是有读操作的时候就不可以写,而写操作只能允许一个线程操作,而且写操作进行时,也不能进行读取操作。
用法和上述Lock的用法差不多,就不再单独写Demo了。
和synchronized关键字的比较;
synchronized是java的一个关键字,也有锁的作用,但是用法比较局限,也有很多synchronized和lock的比较,这里也简单说一下。
synchronized | lock | |
---|---|---|
类型 | 关键字 | 抽象类 |
用法 | 用法单一 | 用法丰富,上文中的tryLock()等方法都是synchronized 不具备的 |
释放 | 自动释放 | 必须手动释放,否则会出现死锁的问题 |
效率 | 较低 | 在调用频繁的情况下,效率会比synchronized 高很多 |
状态 | 无法获取锁状态 | 可以通过Lock得知线程有没有成功获取到锁 |