Java之多线程里面的锁理解以及synchronized与Lock的区别

一、宏观的说下锁的分类

1)锁分为乐观锁、悲观锁

悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的


2)锁分为公平锁、非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。


3)锁分为独享锁、共享锁
独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。

 
二、java中常见具体高并发锁

1)synchronized
synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的,够保证在同一个时刻最多只有一个线程执行同一个对象的同步代码,可保证修饰的代码在执行过程中不会被其他线程干扰

    synchronized(obj) {
      
    }

synchronized实现的机理依赖于软件层面上的JVM,对于Synchronized而言,也是一种悲观锁、非公平锁、也是独享锁、也是互斥锁。


    2)ReentrantLock
    可重入锁,顾名思义,这个锁可以被线程多次重复进入进行获取操作
    Lock实现的机理依赖于特殊的CPU指令,比如执行lock()方法的时候,cpu发出lock指令,比如我们执行unlock的时候,cpu发出lock指令,可以认为不受JVM的约束,并可以通过其他语言平台来完成底层的实现。在并发量较小的多线程应用程序中,ReentrantLock与synchronized性能相差无几,但在高并发量的条件下,synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准,高并发量情况下使用ReentrantLock。

ReentrantLock通过方法lock()与unlock()来进行加锁与解锁操作,与synchronized会被JVM自动解锁机制不同,ReentrantLock加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock必须在finally控制块中进行解锁操作。通常使用方式如下所示

    Lock lock = new ReentrantLock();
     
    try {
     
        lock.lock();
     
    }
     
    finally {
     
        lock.unlock();
     
    }

对于ReentrantLock而言,ReentrantLock在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。这是因为,非公平锁实际执行的效率要远远超出公平锁、ReentrantLock也是互斥锁、也是独享锁。


3)Semaphore
互斥是进程同步关系的一种特殊情况,相当于只存在一个临界资源,因此同时最多只能给一个线程提供服务。但是,在实际复杂的多线程应用程序中,可能存在多个临界资源,这时候我们可以借助Semaphore信号量来完成多个临界资源的访问
,通过acquire()与release()方法来获得和释放临界资源,Semaphore和ReentrantLock用法差不多,Semaphore的锁释放操作也由手动进行,因此与ReentrantLock一样,为避免线程因抛出异常而无法正常释放锁的情况发生,释放锁的操作也必须在finally代码块中完成,
构造方法里面也可以设置否公平锁的初始化方式,默认为非公平锁。


4)AtomicInteger
在多线程程序中,诸如++i或i++等运算不具有原子性,是不安全的线程操作之一。通常我们会使用synchronized将该操作变成一个原子操作,但JVM为此类操作特意提供了一些同步类,使得使用更方便,且使程序运行效率变得更高。通常AtomicInteger的性能是ReentantLock的好几倍。

 
三、各个锁的优势

1.synchronized:

在资源竞争不是很激烈的情况,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,synchronized它是通过悲观锁实现的。


2.ReentrantLock:

在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍,而ReentrantLock确还能维持常态。

高并发量情况下使用ReentrantLock。


3.Atomic:

和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步,是基于cas操作来实现的,它是通过乐观锁来实现的。
 

参考地址:https://youzhixueyuan.com/4-kinds-of-java-thread-locks.html  然后自己再对比比较和精简分析


4 synchronized与Lock的区别

1).首先synchronized是java内置关键字,是在在jvm层面,Lock是一个接口,最后是由CPU来发送lock和unlock指令,这个和volatile底层原理实现类似。

2).synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

3 ) .synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

4) .用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

5) .synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

6) .Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
 

 作者:chen.yu
深信服三年半工作经验,目前就职游戏厂商,希望能和大家交流和学习,
微信公众号:编程入门到秃头 或扫描下面二维码
零基础入门进阶人工智能(链接)