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

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

其他知识点

Java 多线程基础
深入理解aqs
ReentrantLock用法详解
深入理解信号量Semaphore
深入理解并发三大特性
并发编程之深入理解CAS
深入理解CountDownLatch
Java 线程池

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TizzyGoodhealth

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值