zoukankan      html  css  js  c++  java
  • Lock接口

    Lock与synchronized

      Lock和synchronized在功能上是一样的。不过Lock提供了一些其他功能,包括定时的锁等待、可中断的锁等待、公平性,以及实现非块结构的加锁

    从性能上Lock的实现类ReentrantLock在JDK5.0之前要好于synchronized,在JDK6.0之后,synchronized做了优化,所以两者的性能相差无几了。

    那在使用上应该选择哪个呢?在《Java并发编程实战》中有一句话:"仅当内置锁不能满足需求时,才可以考虑使用ReentrantLock"。在一些内置锁无法

    满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可

    中断的锁获取操作,公平队列,以及非块结构的锁。否则,还是应该优先使用synchronized。

      在JDK5.0中,内置锁与ReentrantLock相比还有另外一个优点:在线程转储中能给出在哪些调用帧中获得了哪些锁,并能够检测出发生死锁的线程。JVM

    并不知道哪些线程持有ReentrantLock,因此在调试使用ReentrantLock的线程问题时,将起不到帮助作用。JDK6.0解决了这个问题,它提供了一个管理和调试

    接口,锁可以通过该接口进行注册,从而与ReentrantLock相关的加锁信息就能出现在线程转储中,并通过其他的管理接口和调试接口来访问。

      在内置锁中,死锁是一个很严重的问题,恢复程序唯一的方法是重新启动程序,而防止死锁的唯一方法就是在构造程序时避免出现不一致的锁顺序。

    ReentrantLock有可定时与可轮询的功能,这就从另一个方面避免了死锁的发生(注意,使用ReentrantLock一定要在finally中释放锁)。

    Lock的实现类ReentrantLock(重入锁)

      重入锁ReentrantLock,顾名思义,就是支持重进入的锁(synchronized隐式的支持重进入),它表示该锁能够支持一个线程对资源的重复加锁。除此之外,

    该锁还支持获取锁时的公平和非公平性选择。

      1、ReentrantLock的基本使用。

    package org.burning.sport.javase.thread.reentrantlock;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockTest implements Runnable{
        private static Lock lock = new ReentrantLock();
        private int i;
    
        @Override
        public void run() {
            while(true) {
                increment();
            }
        }
    
        public void increment() {
            try {
                lock.lock();
                i++;
                System.out.println(Thread.currentThread().getName() + ":" + i);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                //一定要在finally这里解锁,否则就是定时炸弹
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            ReentrantLockTest test = new ReentrantLockTest();
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test);
            Thread t3 = new Thread(test);
    
            t1.setName("线程一:");
            t2.setName("线程二:");
            t3.setName("线程三:");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }

       2、锁的公平性

        如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。

      public ReentrantLock(boolean fair); 默认是非公平的。公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换影响性能,非公平的锁则会造成线程的 “饥饿”。

      3、Lock的中断响应

        线程的中断 中可以看到,Lock是可以响应线程触发的中断的。只要在获取锁的时候用 lock.lockInterruptibly();就可以了。

      4、轮询锁与定时锁

        lock.tryLock();  和 lock.tryLock(5, TimeUtil.SECONDS);  在内置锁中,死锁是一个很严重的问题,恢复程序唯一的方法是重新启动程序,而防止死锁的唯一方法就是在

      构造程序时避免出现不一致的锁顺序。ReentrantLock有可定时与可轮询的功能,这就从另一个方面避免了死锁的发生(注意,使用ReentrantLock一定要在finally中释放锁)。 

    public class TryLockTest implements Runnable {
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            try {
                if (lock.tryLock(5, TimeUnit.SECONDS)) {
                    lock.lock();
                    //do something .....
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
    
        }
    }

       5、ReentrantLock的好搭档Condition条件

        Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的new Condition()方法)

      创建出来的,换句话说,Condition是依赖Lock对象的。

      示例代码:

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ConditionUseCase {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
    
        public void conditionWait() throws InterruptedException {
            lock.lock();
            try {
                condition.await();
            } finally {
                lock.unlock();
            }
        }
    
        public void conditionSignal() throws InterruptedException {
            lock.lock();
            try {
                condition.signal();
            } finally {
                lock.unlock();
            }
        }
    }

       Condition的(部分)方法及描述:

      void await() throws InterruptedException   

      当前线程进入等待状态直到被通知(signal)或中断,当前线程将进入运行状态且从await()方法返回的情况,包括:

      其他线程调用该Condition的signal()或signalAll()方法,而当前线程被选中唤醒

         □ 其他线程(调用interrupt()方法)中断当前线程;

      □ 如果当前等待线程从await()方法返回,那么表明该线程已经获取了Condition对象所对应的锁;

           void awaitUninterruptibly() 

      当前线程进入等待状态直到被通知,从方法名称上可以看出该方法对中断不敏感

      long awaitNanos(long nanosTimeout) throws InterruptedException 

      当前线程进入等待状态直到被通知、中断或者超时。返回值表示剩余的时间,如果在nanosTimeout纳秒

        之前被唤醒,那么返回值就是(nanosTimeout - 实际耗时) ,如果返回值是0或者负数,那么可以认定已经超时了

      boolean awaitUntil(Date deadline) throws InterruptedException 

      当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定时间就被通知,方法返回true,否则,

      表示到了指定时间,方法返回false

        void signal()  唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁

      void signalAll()  唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须获得与Condition相关联的锁。

       示例代码:

      https://gitee.com/play-happy/base-project/blob/developer/src/main/java/org/burning/sport/javase/thread/reentrantlock/condition/WaxOmatic.java

    线程阻塞工具类:LockSupport

      LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,它弥补了由于resume()在前发生,

      导致线程无法记录执行的情况。和Object.wait()相比,它不需要先获得某个对象的锁,也不会抛出InterruptedException异常。

      LockSupport提供的阻塞和唤醒的方法

      void park()   阻塞当前线程,如果调用 unpark(Thread thread) 方法或者当前线程被中断,才能从park方法返回

      void parkNanos(long nanos)  阻塞当前线程,最长不超过nonos纳秒,返回条件在park()的基础上增加了超时返回

      void parkUntil(long deadline)  阻塞当前线程,知道deadline时间(从1970年开始到deadline时间的毫秒数)

      void unpark(Thead thread)  唤醒处于阻塞状态的线程Thread

      LockSupport sample demo:

    package org.burning.sport.javase.thread.reentrantlock.lock.support;
    
    import java.util.concurrent.locks.LockSupport;
    
    public class LockSupportTest {
        private static Object u = new Object();
        static ChangeObjectThread thread1 = new ChangeObjectThread("t1");
        static ChangeObjectThread thread2 = new ChangeObjectThread("t2");
        public static class ChangeObjectThread extends Thread {
            public ChangeObjectThread(String threadName) {
                super.setName(threadName);
            }
    
            @Override
            public void run() {
                synchronized (u) {
                    System.out.println("in " + getName());
                    LockSupport.park();
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            thread1.start();
            Thread.sleep(100);
            thread2.start();
            LockSupport.unpark(thread1);
            LockSupport.unpark(thread2);
            thread1.join();
            thread2.join();
    
        }
    }

    读写锁 ReentrantReadWriteLock

      1、使用示例:

    package org.burning.sport.javase.thread.readwritelock;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class Cache {
        static Map<String, String> map = new HashMap<>();
        static ReadWriteLock lock = new ReentrantReadWriteLock();
        static Lock readLock = lock.readLock();
        static Lock writeLock = lock.writeLock();
    
        public static final Object get(String key) {
            readLock.lock();
            try {
                return map.get(key);
            } finally {
                readLock.unlock();
            }
        }
    
        public static final void put(String key, String value) {
            writeLock.lock();
            try {
                map.put(key, value);
            } finally {
                writeLock.unlock();
            }
        }
    
        public static void clear() {
            writeLock.lock();
            try {
                map.clear();
            } finally {
                writeLock.unlock();
            }
        }
    }

       2、读写锁的实现分析:

      2.1、读写状态设计

        读写锁同样依赖自定义同步器来实现同步功能。而读写状态就是其同步器的同步状态。同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在

      同步状态(一个整形变量)上维护多个读线程和一个写线程的状态。如果在一个整形变量上维护多种状态,就一定需要 “按位切割使用” 这个变量,读写锁将变量切分为了两

      个部分,高16位表示读,低16位表示写。

      2.2、写锁的获取与释放

        写锁是一个支持重入的排它锁。如果当前线程已经获取了写锁,则增加写状态。写锁的释放与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态

      为0时表示写锁已被释放。

      2.3、读锁的获取与释放

        读锁是一个支持重入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问(或者写状态为0)时,读锁总会被成功的获取,而所做的也只(线程安全的)是增加读

      状态。读锁的每次释放均减少读状态,减少的值是(1<<16)。

      2.4、锁降级

        锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有)写锁,

      再获取到读锁,随后释放写锁的过程。(ReentrantReadWriteLock不支持锁升级)

    参考:

      【1】《Java并发编程的艺术》,方腾飞

      【2】《Java并发编程实战》,童云兰

      【3】《Java高并发程序设计》,葛一鸣

      【4】《Think In Java》,第21章 并发

        

  • 相关阅读:
    数据结构与算法——优先队列类的C++实现(二叉堆)
    Effective C++--经验条款
    [精]读览天下免费阅读平台
    团队现状与用人标准——揭秘万达电商(6)
    稀疏向量计算优化小结
    漫谈雪崩
    Git起步
    Solr 配置文件之schema.xml
    Shader toy (顺手写两个Gyro)(纯代码写3D)
    Tomcat服务器安装
  • 原文地址:https://www.cnblogs.com/happyflyingpig/p/9763129.html
Copyright © 2011-2022 走看看