1.概述
锁是一种工具,用于控制对共享资源的访问
Lock和synchronized,这两个是最常见的锁,它们都可以达到线程安全的目的,但是在使用上和功能上又有较大的不同。
Lock并不是用来代替synchronized的,而是当使用synchronized不合适或不足以满足要求的时候,来提供高级功能的。
Lock接口最常见的实现类,是ReentrantLook
通常情况下,Lock只允许一个线程来访问这个共享资源,不过有时候,一些特殊的实现也可允许并发访问。比如,ReadWriteLock里面的ReadLock。
- 为什么synchronized不够用
1.效率低:所得释放情况少,试图获得锁的时候不能设定超时,不能中断一个正在试图获得锁的线程
2.不够灵活:枷锁和释放锁时机单一,每个所仅有单一的条件某个对象,可能是不够的。
2Lock锁的主要方法介绍
- 在lock声明了四个方法来获取锁
- lock(),tryLock(),tryLock(long time,TimeUnit unit)和LockInterruptibly()
3lock()
lock()就是最普通的获取锁。如果锁已被其他线程获取,则进行等待
Lock()不会像synchronized一样在异常时自动释放锁
因此最佳实践是,在finally中释放锁,以保证发生异常时锁一定被释放
package cn.butool.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock()不会像synchronized一样在异常时自动释放锁
* 因此最佳实践是,在finally中释放锁,以保证发生异常时锁一定被释放
*/
public class MustUnLock {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
// 加锁
lock.lock();
try{
//获取本锁,保护的资源
System.out.println(Thread.currentThread().getName()+"开始执行任务");
}finally {
// 释放锁
lock.unlock();
}
}
}
4tryLock()、tryLock(long time,TimeUnit unit)
- lock()方法不能被中断,一旦陷入死锁,这会带来很大的隐患lock()就会陷入永久等待 ;
- tryLock()用来尝试获取锁,如果当前锁没有被其他线程占用,则获取成功,则返回true,否则返回false,代表获取锁失败。
- 该方法会立即返回,即便在拿不到锁时不会一直在那等
- tryLock(long time,TimeUnit unit)超时就放弃
5用tryLock来避免死锁
5.1代码
package cn.butool.lock;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 用TryLock来避免死锁
*/
public class TryLockDeadLock implements Runnable{
public static void main(String[] args) {
TryLockDeadLock r1 = new TryLockDeadLock();
TryLockDeadLock r2 = new TryLockDeadLock();
r1.flag=1;
r2.flag=0;
new Thread(r1).start();
new Thread(r2).start();
}
int flag =1;
static Lock locl1 = new ReentrantLock();
static Lock locl2 = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(flag ==1){
try {
if(locl1.tryLock(800, TimeUnit.MILLISECONDS)){
try {
System.out.println("线程1获取到了锁1");
Thread.sleep(new Random().nextInt(1000));
if(locl2.tryLock(800, TimeUnit.MILLISECONDS)){
try{
System.out.println("线程1获取到了锁2");
System.out.println("线程1获取到了2把锁");
break;
}finally {
locl2.unlock();
}
}else{
System.out.println("线程1获取锁2失败,已重试");
}
}finally {
locl1.unlock();
Thread.sleep(new Random().nextInt(1000));
}
}else{
System.out.println("线程1获取锁1失败,已重试");
}
} catch (Exception e) {
e.printStackTrace();
}
}
if(flag ==0){
try {
if(locl2.tryLock(3000, TimeUnit.MILLISECONDS)){
try {
System.out.println("线程2获取到了锁2");
Thread.sleep(new Random().nextInt(1000));
if(locl1.tryLock(800, TimeUnit.MILLISECONDS)){
try{
System.out.println("线程2获取到了锁1");
System.out.println("线程2获取到了2把锁");
break;
}finally {
locl1.unlock();
}
}else{
System.out.println("线程2获取锁1失败,已重试");
}
}finally {
locl2.unlock();
Thread.sleep(new Random().nextInt(1000));
}
}else{
System.out.println("线程2获取锁1失败,已重试");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
5.2打印
线程1获取到了锁1
线程2获取到了锁2
线程2获取锁1失败,已重试
线程1获取到了锁2
线程1获取到了2把锁
线程2获取到了锁2
线程2获取到了锁1
线程2获取到了2把锁
6LockInterruptibly()
相当于tryLock(long time, TimeUnit unit)把超时时间设置为无限。在等待锁的过程中,线程可以被中断
6.1代码
package cn.butool.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockInterruptiblyTest implements Runnable{
public static void main(String[] args) {
LockInterruptiblyTest lockInterruptiblyTest = new LockInterruptiblyTest();
Thread thread1 = new Thread(lockInterruptiblyTest);
Thread thread2 = new Thread(lockInterruptiblyTest);
thread1.start();
thread2.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();
}
private Lock lock = new ReentrantLock();
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"尝试获取锁");
try {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName()+"拿到了锁");
Thread.sleep(5000);
}catch (Exception e){
System.out.println(Thread.currentThread().getName()+"睡眠期间被中断了");
}
finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放了锁");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"等锁期间被中断了");
}
}
}
6.2打印
第一种情况Thread-1拿到了锁:
Thread-0尝试获取锁
Thread-1尝试获取锁
Thread-1拿到了锁
Thread-0等锁期间被中断了
Thread-1释放了锁
第二种情况Thread-0拿到了锁:
Thread-0尝试获取锁
Thread-1尝试获取锁
Thread-0拿到了锁
Thread-0睡眠期间被中断了
Thread-0释放了锁
Thread-1拿到了锁
Thread-1释放了锁
7unLock()解锁
最应该被写到finally里面,并且是第一时间写到finally里面,否则有可能会漏掉,如果漏掉有可能会使程序陷入死锁。
finally { lock.unlock(); System.out.println(Thread.currentThread().getName()+"释放了锁"); }
8可见性保证
在进入synchronized锁时,会去主存中读取此时的最新数据,退出锁时将当前更新刷新到主存中
lack的加解锁和synchronized有同样的内存语义,也就是说下一个线程加锁后可以看到所有前一个线程解锁前发生的所有操作