> 文档中心 > 自定义一个显示锁,并且这个锁存在超时机制

自定义一个显示锁,并且这个锁存在超时机制


synchronized 锁存在几个问题

我们在使用synchronized锁时候,由于synchronized(锁升级和锁竞争本文不讨论)特定锁机制,导致我们不能针对锁进行控制
如果到获取到锁,但迟迟没释放锁,或者说执行时间过长,但是没有出现异常,就不会释放锁。

static final Object lock = new Object();    synchronized (lock){   while (true){  //代码     } }

如上述代码,除非这段代码执行完,锁才会释放。但此时代码是死循环,就不会一直释放锁,除非当前运行的这个线程死亡。

如一些场景中,我们可能需要业务最大执行10s,在10s内不管你有没有执行结束,都不等待你了,直接释放锁。

类似于Futureget()方法,指定时间内没获取到异步结果,就超时,直接抛出异常,当然结果也不会获取到结果。

 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()方法才会释放锁。
  • 代码块
    只能是异常或者执行结束才会释放锁