分享

java多线程——显式锁 vs 隐式锁

 小王曾是少年 2022-08-24 发布于江苏

所谓显式锁和隐式锁,主要指的就是 synchronized 关键字和  ReentrantLock 类。

下面具体聊一聊二者之间的区别:

1 底层不同

synchronized 是java中的关键字,是JVM层面的锁。

ReentrantLock 是是JDK5以后出现的具体的类。使用lock是调用对应的API。

我们通过Javap命令来查看调用二者的汇编指令:

可以看出 synchronized 是底层是通过monitorenter进行加锁,通过monitorexit来退出锁的。

ReentrantLock 对象是通过调用对应的API方法来获取锁和释放锁的。

2  使用方式不同 

正如文章的标题那样,显式锁和隐式锁最大的不同就是在使用的时候,程序员要不要手动写代码去获取锁和释放锁的操作。

在使用 synchronized 关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当synchronized 代码块执行完成之后,系统会自动的让程序释放占用的锁。 如下:

public class Demo9 {
    public static void main(String[] args) {
        //线程不安全
        //解决方案2  同步方法
        Runnable runnable = new Ticket();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }

    static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        @Override
        public void run() {
            while (true) {
                boolean flag = sale();
                if(!flag){
                    break;
                }
            }
        }
        
        public synchronized boolean sale(){
            if (count > 0) {
                //卖票
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
                return true;
            }
                return false;
        }
    }
}

在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。

  • 手动获取锁方法:
lock.lock();
  • 手动释放锁:
lock.unlock();

举例如下:

public class Demo10 {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        private Lock l = new ReentrantLock();
        @Override
        public void run() {
            while (true) {
                l.lock();
                    if (count > 0) {
                        //卖票
                        System.out.println("正在准备卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
                    }else {
                        break;
                    }
                l.unlock();
            }
        }
    }
}

3 是否可以中断

synchronized 是不可中断的。除非抛出异常或者正常运行完成

lock可以中断的。中断方式:

  • 调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断

4 是否是公平锁

synchronized 是非公平锁

lock创建时可以传入一个Boolean值来设置是公平锁还是非公平锁:

  • true:公平锁
  • false:非公平锁

5 性能

在java1.6之前synchronized性能较低,因为其需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还要多,相比之下lock的性能更高一些。

然而到了java1.6之后对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁导致synchronized的性能也不差,并且由于其在语义上很清晰所以也被官方更多的支持。

6 使用锁的方式

lock 获取锁与释放锁的过程,都需要程序员手动的控制 Lock用的是乐观锁方式。

  • 乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

synchronized托管给jvm执行 原始采用的是CPU悲观锁机制

  • 悲观锁:线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多