如何用Java模拟一把锁

我们常用到加锁方式有synchronized关键字,还有ReentrantLock,那如何利用Java模拟一把锁。

开始构思:

加锁就是为了让任何时刻,都只有一个线程访问共享资源,如果共享资源已经被线程占用,那其他线程来了只能选择等待,占用线程释放资源以后再唤醒其他等待线程。于是锁的基本样子就呼之欲出了,简单粗暴:

public synchronized void lock() {

try {

    wait();

} catch (InterruptedException e) {

    e.printStackTrace();

}

}

public synchronized void unlock() {

notifyAll();

}

}

创建一个包含上锁,解锁方法的Java类,上锁的时候思路就是等待,解锁的时候思路就是唤醒,大概的模样就有了。注意这里上锁、解锁方法都使用synchronized修饰,请读者思考下为什么?

逻辑推翻,代码优化

上面我们实现的锁无法使用,上锁方法一进来就等待,那所有线程都处于等待状态了,没有线程可以获取锁,显然是错误的。

推翻了自己,我们继续构思并优化上述代码。当有线程进入时,我们需要检查资源是否已上锁,如果是,线程等待,否则,线程占有资源并标记资源已上锁。于是,我们需要增加一个锁标记,用来标识资源是否已被占用:

    private boolean locked = false;

    public synchronized void lock() {

        try {

            while(locked) {

                wait();

            }

        locked = true;

    } catch (InterruptedException e) {

        e.printStackTrace();

}

}

public synchronized void unlock() {

    locked = false;

    notifyAll();

}

优化后代码貌似可以基本使用,我们增加了一个锁标记,当有线程上锁时,将锁标记置为true,其他线程等待,当线程释放锁时,将锁的状态置为false。注意释放锁的时候要先设置锁的状态,然后再唤醒等待线程。

逻辑优化,代码优化

我们的锁现在基本可以正常使用了,对于同一个对象实例,我们可以成功完成上锁与解锁,但是同一个线程继续获取锁时,还是会继续等待,换句话说,我们的锁不支持可重入。

我们继续构思,可重入是指当一个线程获取对象锁时,这个线程再次访问对象同步共享资源时,可以直接进入,不需要再次获取锁。于是我们需要记录一下当前占有锁的线程:

private boolean locked = false;

private Thread locker = null;

public synchronized void lock() {

    try {

        Thread currentThread = Thread.currentThread();

        while(locked && locker!=currentThread) {

            wait();

        }

        locked = true;

        locker = currentThread;

    } catch (InterruptedException e) {

        e.printStackTrace();

}

}

public synchronized void unlock() {

locked = false;

notifyAll();

}

当有线程占有资源时,我们修改锁的标记并且记录占有锁的线程,当线程再次请求锁时,如果请求线程和占有锁线程是同一个线程,则可以继续访问同步共享资源,无需再次获取锁,这样就实现了可重入锁的功能。

小记:

我们基本实现了一把可重入锁,但是可重入锁的获取次数我们没有记录,释放锁的时候,我们粗鲁的唤醒所有线程。so减少无用的唤醒操作次数,严谨的执行唤醒操作,我们可以加入一个锁count计数,当线程重入锁时,执行计数incr,锁释放时,执行计数decr,当执行释放计数为0时,执行唤醒操作。请读者自行思考下如何实现?

喜欢我可以关注公众号:码农小麦

猜你喜欢

转载自blog.csdn.net/weixin_43275277/article/details/88545681