Synchronized和Lock的区别,以及有关lock锁的冷知识

6 篇文章 0 订阅

正文:

Synchronized和Lock的区别

synchronized关键字和java.util.concurrent.locks.Lock都能加锁,两者有什么区别呢?

  1. 原始构成sync是JVM层面的,底层通过monitorentermonitorexit来实现的。Lock是JDK API层面的。(sync一个enter会有两个exit,一个是正常退出,一个是异常退出)
  2. 使用方法sync不需要手动释放锁,而Lock需要手动释放。
  3. 是否可中断sync不可中断,除非抛出异常或者正常运行完成。Lock是可中断的,通过调用interrupt()方法。
  4. 是否为公平锁sync只能是非公平锁,而Lock既能是公平锁,又能是非公平锁。
  5. 绑定多个条件sync不能,只能随机唤醒。而Lock可以通过Condition来绑定多个条件,精确唤醒。
synchronized&ReentrantLock

synchronized和ReentrantLock都称为可重入锁,也称递归锁. 指的同一个线程在外层方法获得锁时,进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有锁的代码块。比如get方法里面有set方法,两个方法都有同一把锁,得到了get的锁,就自动得到了set的锁。

就像有了家门的锁,厕所、书房、厨房就为你敞开了一样。可重入锁可以避免死锁的问题。

代码如下:

public class ReentrantLockDemo {
    public static void main(String[] args) {
        Phone phone=new Phone();
        syncTest(phone);
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread t1=new Thread(phone);
        Thread t2=new Thread(phone);
        t1.start();
        t2.start();
    }

    private static void syncTest(Phone phone) {
        new Thread(()->{
            try{
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(()->{
            try{
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"t2").start();
    }
}

class Phone implements Runnable{
    //Synchronized TEST
    public synchronized void sendSMS(){      System.out.println(Thread.currentThread().getId()+"\t"+"sendSMS()");
        sendEmail();
    }
    
    public synchronized void sendEmail(){
   System.out.println(Thread.currentThread().getId()+"\t"+"sendEmail()");
    }
    
    //Reentrant TEST
    Lock lock=new ReentrantLock();
    @Override
    public void run() {
        get();
    }
    public void get(){
        //lock.lock();//mark1
        lock.lock();
        try{
        System.out.println(Thread.currentThread().getId()+"\t"+"get()");
            set();
        }finally {
            //lock.unlock();//mark2
            lock.unlock();
        }
    }
    public void set(){
        lock.lock();//mark3
        try{
        System.out.println(Thread.currentThread().getId()+"\t"+"set()");
        }finally {
            lock.unlock();//mark4
        }
    }
}

运行结果:

12	sendSMS()
12	sendEmail()
13	sendSMS()
13	sendEmail()
14	get()
14	set()
15	get()
15	set()

由以上代码可知, synchronized和ReentrantLock都能实现线程同步,保证结果的一致性.

思考:可以加多个锁或解多个锁吗?

可以, 但加锁的数量和解锁的数量必须相等。

这个问题在阿里巴巴的面试中有被问到,不妨我们来用代码证明一下。

在注释掉以上的代码的mark1和mark2时,运行的结果正常如下:

14	get()
14	set()
15	get()
15	set()
(1).当将mark1和mark2同时打开时

结果同上

(2).当打开mark1关闭mark2时
12	get()
12	set()

编译通过,但只出来第1个线程的结果, 相当于第二个线程被阻塞, 编译还在进行。

这是因为第1个线程的锁还没有被释放,导致第2个线程无法加锁。而导致第2个线程处于的一个等待的状态。

(3).当关闭mark1打开mark2时

程序会正常执行完毕,但是会报一个异常(非法监视器异常)。

12	get()
12	set()
13	get()
13	set()
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at 
    ...

这个情况是由一个没有被加锁的线程而强制释放一个所导致的。

(4).当关闭mark1, 打开mark2, 注释掉mark4时

这时我们会发现程序照样执行正常完毕, 那是因为mark2处的锁释放了mark3处的锁, 从而不会抛出异常。

13	get()
13	set()
12	get()
12	set()

通过分析以上4种情况,我们大致可以得出一个规律: 当一个锁加加锁的个数和解锁的个数数量相等的时候,无论是否在一个方法里面, 程序都能正常执行完毕, 能保证结果的一致性和正确性。但在一般情况下,对于一个线程或一个方法,通常只加一个锁和解一个锁。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值