自定义一个显示锁,并且这个锁存在超时机制
synchronized 锁存在几个问题
我们在使用synchronized锁时候,由于synchronized(锁升级和锁竞争本文不讨论
)特定锁机制,导致我们不能针对锁进行控制
如果到获取到锁,但迟迟没释放锁,或者说执行时间过长,但是没有出现异常,就不会释放锁。
static final Object lock = new Object(); synchronized (lock){ while (true){ //代码 } }
如上述代码,除非这段代码执行完,锁才会释放。但此时代码是死循环,就不会一直释放锁,除非当前运行的这个线程死亡。
如一些场景中,我们可能需要业务最大执行10s,在10s内不管你有没有执行结束,都不等待你了,直接释放锁。
类似于Future
的get()
方法,指定时间内没获取到异步结果,就超时,直接抛出异常,当然结果也不会获取到结果。
try { String future = new FutureTask<>(()->{ return ""; }).get(5,TimeUnit.SECONDS); } catch (ExecutionException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); }
解决办法
我们都知道JDK1.5之后提供了一个新的锁也就是Lock接口ReentrantLock
锁
- 使用JDK提供的
ReentrantLock
显示锁 - 自己定义显示锁
- 自己在
synchronized
代码块里判断,执行时间大于多少,抛异常会自动释放锁
这里我们不讲述Lock锁
我们这里自己定义一个显示锁,了解一下锁释放的机制
synchronized锁
- 隐式锁
- 自动释放锁
- 不能手动释放锁
- 遇到异常会自动释放
- wait()会释放,其他线程去抢占锁
基于以上释放锁的原理,我们来定义一个Lock锁
首先分析一下需要加锁
和解锁
,加锁时候指定超时时间
如果锁被占用了,其他的线程我们可以保存下来,也就是阻塞队列。
注意
: 谁加锁,谁解锁
对此我们的接口如下
public interface Lock { //加锁 void lock() throws TimeoutException; //释放锁 void unLock(); // 超时 void lock(long millio) throws TimeoutException; //获取正在等待的线程 int getWaitThreadList();}
实现方法
package thread.thread.synchronizedtest;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.TimeoutException;public class TryLock implements Lock { //等待线程 private List<Thread> threadList; //锁是否在使用 private volatile boolean isUsed = false; private Thread cunrrentThread; public TryLock() { this.threadList = new ArrayList<>(); } @Override public synchronized void lock() throws TimeoutException { //System.out.println(Thread.currentThread().getName() + " " + " get lcok "); //锁被使用 也就是被获取 while (isUsed) { //加入阻塞队列 threadList.add(Thread.currentThread()); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //当前加锁的线程 cunrrentThread = Thread.currentThread(); //加锁 isUsed = true; System.out.println(Thread.currentThread().getName() + " get lcok"); } @Override public synchronized void unLock() { //释放锁 谁加锁 谁释放锁 if (Thread.currentThread() == cunrrentThread) { threadList.remove(Thread.currentThread()); isUsed = false; System.out.println(Thread.currentThread().getName() + " rem lcok");this.notifyAll(); } } @Override public synchronized void lock(long millio) throws TimeoutException { if (millio <= 0) { this.lock(); } long timeout = System.currentTimeMillis() + millio; this.lock(); while (isUsed && Thread.currentThread() == cunrrentThread) { //超时 if (System.currentTimeMillis() - timeout >= 0) { //释放锁 isUsed = false; throw new TimeoutException(Thread.currentThread().getName() + " ---> time out "); } } } @Override public int getWaitThreadList() { //获取的时候不能被改变 防止获取等待线程数量时候,线程对其改变 return Collections.unmodifiableList(threadList).size(); }}
最后我们来测试
首先是不超时的锁
TryLock lock = new thread.thread.synchronizedtest.TryLock(); for (int i = 0; i < 5; i++) { new Thread(() -> { try { lock.lock(); //这里模拟执行多少时间 TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } catch (TimeoutException timeOutExpection) { System.out.println(Thread.currentThread().getName() + " lcok time out "); } finally { lock.unLock(); } }, "t->>" + i).start(); } Thread.sleep(2);
结果如下,可以看到始终是一个get lock ,rem lock之后 下一个线程才会抢到锁
超时锁
//省略 lock.lock(5000); //省略
运行可以看到如下结果
始终是超时之后其他线程才会抢锁
抛异常解决
判断时间,抛异常释放锁
Random random = new Random(System.currentTimeMillis()); final Object lock = new Object(); for (int i = 0; i < 6; i++) { new Thread(() -> { long end = System.currentTimeMillis() + 3; // 3s超时 synchronized (lock) { System.out.println(Thread.currentThread().getName() + " get lock "); try { Thread.sleep(random.nextInt(7)); } catch (InterruptedException e) { e.printStackTrace(); } if (System.currentTimeMillis() - end >= 0) { throw new RuntimeException(Thread.currentThread().getName() + " time out "); } System.out.println(Thread.currentThread().getName() + " done "); } }, "thread:" + i).start(); }
当然我们可以看到,只有睡眠0s 没有睡眠的这个线程正常运行完了,其他的都执行时间大于等于3s 都抛出异常,并且释放锁。
为什么2s也抛异常了,2s这个先睡眠,再判断 再抛异常,这些都需要时间。
最后
- 基于对象锁
synchronized 释放锁完全是基于内部代码机制监控异常和执行结束以及锁对象的wait()方法才会释放锁。 - 代码块
只能是异常或者执行结束才会释放锁