显式锁与隐式锁的区别

在面试的过程中有可能会问到:在Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁,其中它们分别是什么?两者的区别是什么?所谓的显式锁和隐式锁的区别说的也是Synchronized和Lock的区别。

本文主要内容:将通过六个方面详细介绍sync和lock的区别。

1.两者的出身不同

synchronized是Java中的关键字,由JVM维护,是JVM层面的锁;

而lock是JDK5之后才出现的具体的类,使用Lock是调用对应的API,是API层面的锁。

synchronized的底层是通过monitorenter进行加锁的(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖monito对象的,只有在同步块或者是同步方法中才可以调用wait/notify方法。因为只有在同步块或同步方法中,JVM才会调用monitor对象的。);通过monitorexit来退出锁的。

Lock则是通过调用相应的API方法来获取和释放锁
显式锁与隐式锁的区别_第1张图片

2.使用方式的不同

synchronized隐式锁;lock是显式锁

显氏锁和隐式锁的区别在于:使用显式锁的时候,使用者需要手动去获取和释放锁。

在使用synchronized关键字的时候,使用者不需要写其他的代码,程序就可以自动获取锁和释放锁。synchronized是由系统维护的,系统会自动的让程序释放占用的锁。

在使用lock的时候,需要使用者手动去获取锁和释放锁。如果没有释放锁,就可能出现死锁的现象。手动获取锁的方法lock.lock(); 释放锁的操作:unlock().
对于synchronized隐式锁使用
1、同步方法体,在方法声明中使用,如下:

public synchronized void method(){
        //方法体
}

2、同步代码块,修饰在代码块外层,指定加锁对象,如下:

public void method2(){
    synchronized (this) {
    //一次只能有一个线程进入   
    }
}

上述synchronized(this)指定了当前对象本身作为锁,和它持有相同对象锁的地方将产生互斥性。当一个线程访问method2的同步代码块时,它就获得了这个object的对象锁。其他的线程对该object所有同步代码部分的访问都被暂时的阻塞。

sychronized的不同写法对程序响应的快慢和对资源高并发的利用程度不一样,性能和执行效率从差到优排序如下:

同步方法体 < 同步代码块 < 小对象锁同步代码块

小对象锁同步代码块指锁的对象的所占内存小,因为锁是对象,加锁和解锁都需要释放资源,那肯定是锁对象越小越好,实际应用如下:

private byte[] lock = new byte[1];
public void method3(){
    synchronized (lock) {
        //一次只能有一个线程进入   
    }
}
package comx.demo;
import java.io.ObjectOutputStream;
public class demo2_1 {
    /**线程安全 1 同步代码块
     *
     * 线程同步:synchronized
     *
     * @param args
     */
    public static void main(String[] args) {
        //线程不安全
        //解决方案1. 同步代码块
        //格式;   synchronnized(锁对象){
        //              }
        
        //多态方式
        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 Object o = new Object();
        @Override
        public void run() {
            // 出现不安全问题,,会出现负数
            while(true){
                //多个人对应一把锁,排队进行
                //如果每个人对应一把自己的锁,则很可能会出现问题
                synchronized (o) {
                    if (count > 0) {
                        //卖票
                        System.out.println("正在准备卖票");
                        try {
                            //休眠1秒,增大不安全概率
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + count);
                    }else{
                        break;
                    }
                }
            }
        }
    }
}
package comx.demo;
public class demo2_2 {
    /**线程安全 2 同步方法
     *
     * 线程同步:synchronized
     *
     * @param args
     */
    public static void main(String[] args) {
        //线程不安全
        //解决方案2 . 同步方法
        //格式;   synchronnized(锁对象){
       //
        //        }
        //多态方式 创建一个任务
        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 Object o = new Object();
        @Override
        public void run() {
            // 出现不安全问题,,会出现负数
            while(true){
              Boolean flag = sale();
              if (!flag){
                  break;
              }
            }
        }
        //增加一个修饰 synchronized 锁
        public synchronized boolean sale(){
            //this 关键字的使用
            /**
             * 使用
             * synchronized(this){
             *
             *          }
             *  使用这种,其他的都无法执行这段代码,需要排队进行
             */
            //如果为静态方法,则使用
            // 类名.class
            // Ticket.class
            if (true) {
                //卖票
                System.out.println("正在准备卖票");
                try {
                    //休眠1秒,增大不安全概率
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + count);
                return true;
            }
            return false;
        }
    }
}

对于Lock类它的使用

package comx.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class demo2_3 {
    /**
     * 同步代码块 和 同步方法   都属于隐式锁
     * 线程同步: Lock
     * @param args
     */
    public static void main(String[] args) {
        //线程不安全
        //解决方案3  显式锁  Lock  子类 ReentranLock
        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;
        // 创建   显式锁   l
        private Lock l = new ReentrantLock();
        @Override
        public void run() {
            // 出现不安全问题,,会出现负数
            while(true) {
                //上锁(获取锁)
                l.lock();
                if (count > 0) {
                    //卖票
                    System.out.println("正在准备卖票");
                    try {
                        //休眠1秒,增大不安全概率
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println("出票成功,余票:" + count);
                }else{
                    break;
                }
                //关闭锁(释放锁)
                l.unlock();
            }
        }
    }
}

3.等待是否中断

synchronized关键字是不可中断的,除非抛出异常或者正常运行结束

而Lock是可以中断的,中断方式:

调用设置超时方法tryLock(Long timeout,timeUnit unit)
调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断。

4.加锁的时候是否公平

synchronized是非公平锁;
而lock两者都可以,默认创建是非公平锁。
注意:什么是公平锁,非公平锁?在这里解释一下:
公平锁:先来先到 ,排队,
非公平锁:线程一起抢

5.锁绑定多个条件来condition

synchronized要么随机唤醒一个线程;要么是唤醒所有等待的线程。

lock:可以用实现分组唤醒需要唤醒的线程,可以精准的唤醒,而不是像synchronized那样,不能精准唤醒线程。

6.性能比较

synchronized是托管给JVM去执行的,而Lock是Java写的控制锁的代码。在Java1.5中,synchronized的性能是低效的,因为其是重量级锁的操作,需要从用户态切换到内核态消耗大量的时间。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6之后,对synchronized关键字进行了优化,有适应性自旋,锁消除,轻量级锁,偏向锁等,导致在Java1.6中synchronized的性能并不比lock差。

在之前的synchronized版本中使用是悲观锁的机制,即线程独占锁,其它线程只能依靠阻塞来等待线程释放的锁,而线程阻塞时会引起线程的上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。

而Lock使用的乐观锁的方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁的实现方式就CAS机制(compareAndSetstate),调用的是CPU提供的底层指令。

转载于:
【Java锁体系】五、隐式锁和显式锁的区别(Synchronized和Lock的区别)

显示锁与隐式锁的区别

隐式锁 Synchronized 与显示锁 Lock的用法和简单对比

你可能感兴趣的:(Java)