• Java并发编程之锁


    一、 Lock 锁

    java.util.concurrent.locks.Lock

    为什么有了synchronized,还需要Lock呢?

    • 使用方式更灵活
    • 性能开销小

    1.1 ReentrantLock

    简单示例:

    public class TestLock {
    
        private Lock lock=new ReentrantLock();
    
    
        private int value;
    
        public void add(){
            try {
                lock.lock();
                value++;
            } finally {
                lock.unlock();
            }
        }
    }
    

    ReentrantLock:可重入锁
    new ReentrantLock(true):可重入锁+公平锁

    可重入锁:某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。 ReentrantLock和synchronized都是可重入锁。

    公平锁:先排队的先获得锁。

    可重入锁的示例:

    public class TestLock {
    
        private Lock lock=new ReentrantLock();
    
        private int value;
    
        public  void add(){
            try {
                lock.lock();
                value++;
                //已经获取了锁,再进入也要获取锁的print方法,不会产生死锁
                print();
            } finally {
                lock.unlock();
            }
        }
    
        public  void print(){
            try {
                lock.lock();
                System.out.println("打印内容");
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new TestLock().add();
        }
    }
    

    1.2 ReentrantReadWriteLock 读写锁

    读写锁示例:

    public class TestLock {
    
        private int sum;
    
        private ReadWriteLock lock=new ReentrantReadWriteLock(true);
    
        public int incrAndGet(){
            try {
                lock.writeLock().lock();
                sum++;
                return sum;
            } finally {
                lock.writeLock().unlock();
            }
        }
    
        public int getSum(){
            try {
                lock.readLock().lock();
                return sum;
            } finally {
                lock.readLock().unlock();
            }
        }
    }
    

    读读不互斥,读写互斥,写写互斥

    适用于读多写少的场景。

    读为什么要加锁呢?
    答:避免读的时候去写。

    1.3 Condition的用法

    使用Condition实现了生产-消费的案例

    public class TestLock {
    
        private int sum;
    
        Lock lock = new ReentrantLock();
    
        Condition notFull  = lock.newCondition();
    
        Condition notEmpty=lock.newCondition();
    
        public void consumer() throws InterruptedException {
            try {
                lock.lock();
                while (sum<=0){
                    notEmpty.await();
                }
                sum--;
                System.out.println("消费者:"+sum);
                notFull.signal();
            } finally {
                lock.unlock();
            }
        }
    
        public void producer() throws InterruptedException {
            try {
                lock.lock();
                while (sum>=20){
                    notFull.await();
                }
                sum++;
                System.out.println("生产者:"+sum);
                notEmpty.signal();
            } finally {
                lock.unlock();
            }
        }
    
    
        public static void main(String[] args) {
            TestLock testLock=new TestLock();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true){
                            testLock.consumer();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true){
                            testLock.producer();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    
    

    1.4 LockSupport 锁当前线程

    使用示例:

    public class TestThread {
    
        public static void main(String[] args) throws InterruptedException {
            ChildThread childThread = new ChildThread(Thread.currentThread());
            childThread.start();
            LockSupport.park();
            System.out.println("主线程结束");
        }
    
    
        private static class ChildThread  extends Thread{
    
            private Thread thread;
    
            public ChildThread(Thread thread) {
                this.thread = thread;
            }
    
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println("模拟一些初始化操作");
                    LockSupport.unpark(thread);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    用锁的最佳实践

    1. 永远只在更新对象的成员变量时加锁
    2. 永远只在访问可变的成员变量时加锁
    3. 永远不再调用其他对象的方法时加锁

    二、并发原子类

    并发原子类所在的包:java.util.concurrent.atomic

    使用示例:

    public class TestLock {
    
       private AtomicInteger atomicInteger=new AtomicInteger();
       
       public int add(){
           return atomicInteger.incrementAndGet();
       }
       
       public int get(){
           return atomicInteger.get();
       }
       
    }
    

    整个AtomicInteger的实现是无锁的。

    实现原理:

    1. volatile保证读写操作都可见
    2. 使用CAS指令,通过自旋重试保证写入
    • CAS是基于Unsafe的API Compare-And-Swap
    • CPU 硬件指令支持 :CAS指令

    LongAdder 对AtomicLong的改进:
    分段思想。

    三、并发工具类

    我们已经有了锁、有了并发原子类,为什么我们还需要并发工具类呢?

    思考以下场景:有一个方法,用10个线程来分别都执行,但是同时只能有4个在执行。用锁的方式就不好实现。所以JDK给我们提供了以下的并发工具类。

    3.1 Semaphore 信号量

    应用场景:同一时间控制并发线程数。

    public class TestLock {
        
        //声明4个permits(许可证)的信号量
        Semaphore semaphore = new Semaphore(4);
    
        public static void main(String[] args) {
            TestLock testLock = new TestLock();
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            testLock.test();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    
        public void test() throws InterruptedException {
            //拿到一个permits(许可证),也可以带参,一个线程拿两个,如:semaphore.acquire(2);
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "在工作");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放一个permits
                semaphore.release();
            }
        }
    
    }
    

    3.2 CountDownLatch 闭锁

    应用场景:Master线程等待所有Worker线程把任务执行完。

    示例:公司有5个人,每个人都完成了手头的工作,老板才下班。

    public class TestLock2 {
    
        CountDownLatch countDownLatch=new CountDownLatch(5);
    
        public static void main(String[] args) throws InterruptedException {
            TestLock2 testLock = new TestLock2();
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        testLock.test();
                    }
                }).start();
            }
            testLock.countDownLatch.await();
            System.out.println("大家都工作完了,老板(监工)可以下班了");
        }
    
        public void test() {
            System.out.println(Thread.currentThread().getName() + "在工作");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + "工作完了");
            }
        }
    
    }
    

    相当于CountDownLatch有一个计数器,调用了await方法后等待在那里了,每调用countDown方法就减一,只有当计数器减为0了,await才被唤醒。

    3.3 CyclicBarrier 栅栏

    场景:任务执行到一定阶段,等其他任务对齐

    在作用上很类似CountDownLatch,只是使用的方法不一样。

    示例:

    public class TestLock2 {
    
        CyclicBarrier cyclicBarrier=new CyclicBarrier(5);
    
        public static void main(String[] args) throws InterruptedException {
            TestLock2 testLock = new TestLock2();
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        testLock.test();
                    }
                }).start();
            }
    
        }
    
        public void test() {
            System.out.println(Thread.currentThread().getName() + "在工作");
            try {
                int i = new Random().nextInt(10);
                TimeUnit.SECONDS.sleep(i);
                System.out.println(Thread.currentThread().getName() + "工作完了");
               cyclicBarrier.await();
                System.out.println("大家都工作完了,老板(监工)可以下班了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    

    CyclicBarrier没有countDown方法,而是都阻塞到await那里,等阻塞的个数达到,就可以放行。

    3.4 AQS 同步队列器

    AQS是AbstractQueuedSynchronizer的缩写,它是构建锁或其他组件的基础。如:CountDownLatch、Semaphore、ReentrantLock。

    里面有两种资源共享方式:独占|共享。 独占的实现如ReentrantLock,共享的实现如 Semaphore 。子类负责实现公平锁或者非公平锁。

    以ReentrantLock为例,里面有一个state变量,如果state为0,则表示未锁定,调用tryAcquire方法独占锁,并将state+1,此后其他线程来就不能占用了,放入队列里去等着。如果还是当前线程调用,那么他是可以再获得锁的,只是state还要累加。(可重入的概念)但是要注意,获得多少次就要释放多少次才行。

    书山有路勤为径,学海无涯苦作舟
  • 相关阅读:
    Lua中的closure、泛型for
    Lua多重继承
    (转)C++ new详解
    C++重载操作符学习
    Lua中使用继承来组装新的环境
    DOS:变量嵌套和命令嵌套
    C++中成员的私有性
    ManualResetEvent 类
    在IIS中部署和注册WCF服务
    ArcGIS Server 10 地图缓存新特性
  • 原文地址:https://www.cnblogs.com/javammc/p/15265058.html
Copyright © 2020-2023  润新知