美文网首页程序员
如何用Java模拟一把锁

如何用Java模拟一把锁

作者: 麦德漂 | 来源:发表于2019-02-27 15:23 被阅读46次

    我们常用到加锁方式有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时,执行唤醒操作。请读者自行思考下如何实现?

    相关文章

      网友评论

        本文标题:如何用Java模拟一把锁

        本文链接:https://www.haomeiwen.com/subject/fbrfuqtx.html