• Java同步锁-synchronized与lock


    Java同步锁-synchronized与lock

    一、synchronized关键字

    1、synchronized简介

    synchronized实现同步的基础:java中每个对象都可以作为锁。当线程试图访问同步代码时,必须先获得对象锁,退出或抛出异常时必须释放锁。

    表现形式为:代码块同步方法同步

    2、synchronized的使用场景

    • 方法同步
    public synchronized void method1(){}复制代码

    锁住的是该对象的一个实例,当不同线程调用该实例对象中该同步方法,线程只有一个得到锁,其余被阻塞。但如果不同线程同时对该类的不同实例对象执行该同步方法,则不会阻塞,因为他们使用不同的锁。

    • 代码块同步
    synchronized(this)( //ToDo}
    或
    synchronized(普通变量){    }复制代码

    同上

    • 静态方法同步
    public synchronized static void method3(){}复制代码

    锁住的是该类,当不同线程调用该类的该static同步方法时,就只能有一个线程获得锁其余线程被阻塞

    • 静态代码块
    synchronized(Test.class){ //ToDo}
    或
    synchronized(静态变量){ //ToDo}复制代码

    同上

    3、synchronized锁升级

    锁一共有4种状态,级别从低到高依次是:无锁状态偏向锁状态轻量级锁重量级锁

    锁可以升级但不能降级,意味着偏向锁升级为轻量级锁后不能降级为偏向锁。

    • 偏向锁

    大所述情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得所的代价更低而引入偏向锁。

    当一个线程访问同步代码并获取锁时,会对对象头和栈帧中的锁记录里保存锁偏向的线程ID,以后该线程再进入和退出同步锁时,不需要进行CAS操作来加锁和解锁,而只是简单地测试一下对象头的Mark Word里是否存储执行当前线程的偏向锁。

    偏向锁作用是:在没有别的线程竞争的时候,一直偏向当前线程,当前线程可以一直执行。

    • 轻量级锁(自旋锁)

    轻量级锁,由偏向锁升级而来。

    偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。

    • 重量级锁

    轻量级锁膨胀之后,就升级为重量级锁。

    重量级锁时依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被称为互斥锁(synchronized就是重量级锁)。

    偏向锁

    优点:加锁和解锁不需要额外的消耗

    缺点:线程存在竞争,会带来额外的锁撤销的消耗

    场景:单一线程访问同步块场景

    轻量级锁

    优点:竞争的线程不会阻塞,提高了程序的响应速度。

    缺点:线程自旋时不释放CPU

    场景:追求响应时间,同步块执行速度非常快。

    重量级锁

    优点:线程竞争不使用自旋,释放CPU

    缺点:线程阻塞,响应时间缓慢。

    场景:追求吞吐量,同步块执行速度较长。

    二、Lock接口

    1、Lock接口

    Lock,锁对象。在Lock接口出现之前,Java程序时靠synchronized关键字实现锁功能。而在Java SE5.0之后并发包中新增了Lock接口来实现锁的功能。

    它能提供synchronized关键字类似的同步功能,但需要显示获得锁和释放锁,至于二者区别后文补充。

    Lock接口的主要方法:

    • void lock()

    执行该方法时,若锁处于空闲状态,当前线程将获得锁。相反,如果锁已经被其他线程持有,则禁止当前线程获得锁。

    • boolean tryLock()

    若锁可用,则获得锁,并立即返回true,否则返回false。

    tryLock()和Lock()的区别在于:

    tryLock()只是尝试获得锁,若锁不可用不会导致当前线程被禁止,当前线程仍然继续往下执行代码。

    Lock()则是一定要获得锁,如果锁不可用,就一直等待,在未获得锁之前,当前线程并不继续向下自行。

    • void unlock()

    执行该方法时,当前线程将释放持有锁,锁只能由持有者释放,若线程没有持有锁,则执行该方法,可能导致异常方式。

    • Condition newCondition()

    条件对象,获得等待通知组件。该组件和当前的锁绑定,当前线程只有获得锁,才会调用该组件的await()方法,调用后,当前线程将释放锁。

    2、Reentrantlock的使用

    Reentrantlock的使用很简单,只需要显示调用,获得同步锁,释放同步锁即可。

    ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
    .....................
    
    try {
        lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
        //操作
    }catch(Exception e){
       //异常处理
    } finally {
        lock.unlock();  //释放锁
    }复制代码

    reentrantlock锁,在高并发的条件下使用的性能远远高于synchronized关键字。

    并且reentratnlock公平和非公平锁的队列都是基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。

    3、ReadWriteLock接口

    ReadWriteLock接口中主要方法如下:

    public interface ReadWriteLock {
        /**
         * Returns the lock used for reading.
         *
         * @return the lock used for reading
         */
        Lock readLock();
    
        /**
         * Returns the lock used for writing.
         *
         * @return the lock used for writing
         */
        Lock writeLock();
    }复制代码

    ReadWriteLock管理一组锁,一个是只读锁,一个是写锁。

    Java并发库中ReentrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性。

    读锁同一时刻允许有多个线程在访问,但是写进程访问时,所有的读线程其他写进程被阻塞

    读写锁维护一对锁,一个读锁和一个写锁,通过读写锁分离,使得并发性相比一般的排它锁有很大的提升。

    ReentrantReadWriteLock读写锁的几个特性

    • 公平选择性
    • 重入性
    • 锁降级

    读写锁例子(程序来源于网上:blog.csdn.net/canot/artic…):

    public class Cache{
      static Map<String,Object> map = new HashMap<String,Object>();
      static ReentrantReadWriteLock  rwl = new ReentrantReadWriteLock();
      static Lock rLock = rwl.readLock();
      static Lock wLock = rwl.writeLock();
    
      //获取一个key对应的value
      public static final Object get(String key){
         r.lock();
         try{
             return map.get(key);
         }finally{
             r.unlock();
         }
      }
    
      //设置key对应的value并返回旧的value
      public static fianl Object put(String key,Object value){
         w.lock();
         try{
            return map.put(key,value);
         }final{
            w.unlock();
         }
      }
    
      //清空缓存
      public static fianl void clear(){
         w.lock();
         try{
            map.clear();
         } finally{
            w.unlock();
         }
      }
    }复制代码
    • 读写锁的锁降级

    锁降级是指写锁降级成为读锁。如果当前线程持有写锁,然后将其释放再获取读锁的过程不能称为锁降级。锁降级指的在持有写锁的时候再获取读锁,获取到读锁后释放之前写锁的过程称为锁释放。

    (程序来源于:blog.csdn.net/qq_38737992…

    public void work() {
            reentrantReadWriteLock.readLock().lock();
     
            if (!update) {
                reentrantReadWriteLock.readLock().unlock();
     
                // 锁降级开始
                reentrantReadWriteLock.writeLock().lock();
                try {
                    if (!update) {
                        // 准备数据
                        ++index;
                        try {
                            TimeUnit.MILLISECONDS.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        update = true;
                    }
                    reentrantReadWriteLock.readLock().lock();
                } finally {
                    reentrantReadWriteLock.writeLock().unlock();
                    // 锁降级结束,降级为读锁
                }
            }
            try {
                // 使用数据
                for (int i=0; i<5; i++) {
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                }
            } finally {
                reentrantReadWriteLock.readLock().unlock();
            }
        }
    复制代码

    三、synchronized和Lock区别

    1、synchronized和Lock区别

    synchronized是关键字,而Lock是接口

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

    synchronized自动释放锁(a线程执行完同步代码会自动释放锁,b线程执行过程中发生异常会释放锁)

    lock需在finally中手动释放锁(unlock()方法释放锁),否则容易造成线程死锁。

    synchronized关键字的两个线程1和线程2,若当前线程1获得锁,线程2等待,如果线程1阻塞,线程2会一直等待下去。

    而lock锁不一定会等待下去,如果尝试获得不到锁,线程可以不用一直等待就结束了。

    synchronized的锁可重入、不可中断、非公平

    lock锁可重入,可中断、可公平

    lock锁适合大量同步的代码的同步问题synchronized锁适合代码少量的同步问题

    不可重入锁:自旋锁、wait()、notify()、notifyAll()

    不可重入锁,即不可递归调用,递归调用会发生死锁

    2、reentrantlock和synchronized区别

    reentrantLock拥有synchronized相同的并发性和内存语义,此外还多列锁投票、定时锁等候和中断所等候

    使用synchronized锁,A不释放,B将一直等待下去

    使用reentrantlock锁,A不释放,B等待一段时间就会中断等待,而干别的事情。

    synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且代码执行时出现异常,JVM会走到哪个释放锁定。但是Lock不行

    在资源竞争不是很激烈的情况下,synchronized的性能优于reentrantlock锁,而竞争激烈的情况下,synchronized的性能下降几十倍,而reentrantlock的性能维持常态。

    • 性能分析

    synchronized多次自旋,以获得锁,在这个过程中等待的线程不会被挂起,因而节省了挂起和唤醒的上下文切换的开销

    reentrantlock不会自旋,而是直接挂起

    因而在线程并发量不大的情况下,synchronized因为拥有自旋锁、偏向锁和轻量级锁的原因,不用将等待线程挂起,偏向锁甚至不用自旋,所以在这种情况下要比reenttrantlock高效。

    默认情况下synchronized非公平锁;reentrantlock默认是非公平锁。

    • 绑定多个条件

    一个Reentrantlock对象可以同时绑定多个Condition对象,

    而在synchronized中,锁对象的wait()notify()notifyAll()方法可以实现一个隐含的条件。

    如果要和多余一个添加关联的时候,synchronized就不得不额外地添加一个锁,而Reentrantlock则无须这么做只需要多次调用new Condition()方法即可。

  • 相关阅读:
    HDOj-1412
    HDOJ-2153
    HDOJ-1002
    紫书 例题 11-5 UVa 10048 (Floyd求最大权值最小的路径)
    紫书 例题11-4 UVa247 (Floyd判断联通)
    最短路模板
    紫书 例题 11-3 UVa 1151 (有边集的最小生成树+二进制枚举子集)
    紫书 例题 11-2 UVa 1395(最大边减最小边最小的生成树)
    紫书 例题 11-1 UVa 12219 (表达式树)
    紫书 习题 8-25 UVa 11175 (结论证明)(配图)
  • 原文地址:https://www.cnblogs.com/beipiaoyizu/p/13680552.html
Copyright © 2020-2023  润新知