zoukankan      html  css  js  c++  java
  • JUC包下的锁

    五、JUC中的锁

    在前面学习了Synchronized锁,回顾synchronized:

    1. 可重入锁。

    2. 锁升级:无锁态 -> 偏向锁 -> 轻量级锁 -> 重量级锁。

    3. 非公平锁

      公平锁和非公平锁:

      当程序加锁时,肯定会有多个线程竞争这把锁,当一个线程获得锁后,那么就会有一个等待队列维护这些等待线程。

      公平锁:线程遵循达到的先后顺序,先来的优先获取锁,后来的后获取锁。这样的话,内部就要维护一个有序队列

      非公平锁:线程到达后直接参与竞争,如果得到锁直接执行,没有得到锁的话,就进入等待队列。

    4. 系统管理

      因为synchronized的原语是 monitorenter,获取锁和释放锁都是jvm通过加监控和退出监控实现的。

    看下面这个题:

    一个线程打印ABCDEFG,另一个线程打印abcdefg,控制两个线程交替打印AaBbCcDdEeFfGg。
    

    首先来看使用Synchronized实现:

    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 赵帅
     * @date 2021/1/13
     */
    public class SynchronizedPrint {
    
        private final Object lock = new Object();
    
        public void fun1() {
            String str = "ABCDEFG";
            char[] chars = str.toCharArray();
            synchronized (lock) {
                for (char aChar : chars) {
                    System.out.print(aChar);
                    try {
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    
        public void fun2() {
            String str = "abcdefg";
            char[] chars = str.toCharArray();
            synchronized (lock) {
                for (char aChar : chars) {
                    System.out.print(aChar);
                    try {
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            SynchronizedPrint print = new SynchronizedPrint();
            new Thread(print::fun1).start();
            new Thread(print::fun2).start();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("
    ");
        }
    }
    

    ReentrantLock

    ReentrantLock是JUC包下的基于CAS实现的锁,因此是一个轻量级锁。查看ReentrantLock的构造方法可以发现ReentrantLock默认为非公平锁。

        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    

    ReentrantLock通过构造方法参数可以选择是公平锁或非公平锁,默认是非公平锁。

        /** 创建公平锁 */
        private final ReentrantLock fairLock = new ReentrantLock(true);
        /** 创建非公平锁 */
        private final ReentrantLock nonfairLock = new ReentrantLock();
    

    ReentrantLock的使用

    ReentrantLock在使用时需要手动的获取锁和释放锁:

    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author 赵帅
     * @date 2021/1/13
     */
    public class ReentrantLockDemo {
    
        private final ReentrantLock lock = new ReentrantLock();
        
        public void fun1() {
            lock.lock();
            try {
                // do something
            }finally {
                lock.unlock();
            }
        }
    }
    

    ReentrantLock需要通过lock.lock()方法获取锁,通过lock.unlock()方法释放锁。而且为了保证锁一定能狗被释放,避免死锁的发生,一般获取锁的操作紧挨着try而且finally的第一行必须为释放锁操作。

    ReentrantLock是可重入锁。

    因为ReentrantLock是手动获取锁因此当锁重入时,每获取一次锁就要释放一次锁。

    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author 赵帅
     * @date 2021/1/14
     */
    public class ReentrantLockDemo1 {
    
        private final ReentrantLock lock = new ReentrantLock();
        
        public void fun1() {
    
            lock.lock();
            try {
                // 锁重入
                lock.lock();
                try {
                    // do something
                    System.out.println("do something");
                }finally {
                    lock.unlock();
                }
            }finally {
                lock.unlock();
            }
        }
    }
    

    上面代码获取了两次锁,所以就需要手动的释放两次锁。

    ReentrantLock的方法:

    常用的方法有:

    • lock(): 获取锁
    • unlock(): 释放锁
    • tryLock():尝试获取锁并立即返回获取锁结果true/false
    • tryLock(long timeout,Timeunit unit): 延迟获取锁,在指定时间内不断尝试获取锁,如果在超时前获取到锁,则返回true,超时未获取到锁则返回 false。会响应中断方法。
    • lockInterruptibly(): 获取响应中断的锁。
    • isHeldByCurrentThread: 当前线程是否获取锁。
    • newCondition()获取一个条件等待对象。

    上述方法的实际使用如下:

    import org.junit.Test;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author 赵帅
     * @date 2021/1/13
     */
    public class ReentrantLockTest {
    
        private final ReentrantLock lock = new ReentrantLock();
    
        @Test
        public void fun1() {
            System.out.println("lock与unlock");
            lock.lock();
            try {
                System.out.println("获取锁");
            } finally{
                lock.unlock();
            }
        }
    
        @Test
        public void fun2() throws InterruptedException {
            Thread thread = new Thread(() -> {
                try {
                    lock.lockInterruptibly();
                    System.out.println("获取到可响应线程中断方法的锁");
                    // 模拟业务耗时
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    System.out.println("线程被中断");
                } finally {
                    lock.unlock();
                }
            }, "thread-1");
            thread.start();
            // 等待线程thread-1启动
            TimeUnit.MILLISECONDS.sleep(200);
            // 中断thread-1线程,此时线程thread-1会抛出InterruptedException异常
            System.out.println("中断thread-1");
            thread.interrupt();
        }
    
        public void run3() {
            if (lock.tryLock()) {
                try {
                    System.out.println(Thread.currentThread().getName() + "获取到锁");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + "未获取到锁");
            }
        }
    
        @Test
        public void fun3() {
            Thread thread1 = new Thread(this::run3, "thread-1");
            Thread thread2 = new Thread(this::run3, "thread-2");
            thread1.start();
            thread2.start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public void run4() {
            try {
                if (lock.tryLock(3L, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                    TimeUnit.SECONDS.sleep(4);
                } else {
                    System.out.println(Thread.currentThread().getName() + "未获取锁");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                // 如果获取锁成功需要释放锁
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                    System.out.println(Thread.currentThread().getName() + "释放锁");
                }
            }
        }
    
        @Test
        public void fun4() throws InterruptedException {
            Thread thread1 = new Thread(this::run4, "thread-1");
            Thread thread2 = new Thread(this::run4, "thread-2");
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
        }
    }
    

    ReentrantLock和synchronized的区别:

    比较项 ReentrantLock synchronized
    可重入 支持 支持
    响应中断 同时支持响应中断lock.lockInterruptibly(),不响应中断 lock.lock() 不支持响应中断
    可控性 手动控制加解锁 系统控制加解锁,人力不可控
    尝试获取锁 tryLock(),立即响应结果 不支持
    延迟获取锁 tryLock(timeout,timeunit)延迟超时获取锁 不支持
    公平锁 支持公平锁和非公平锁 非公平锁

    ReentrantLock如果使用不当,没有释放锁,就会造成死锁,而synchronized是由系统管理加锁和释放锁。

    Condition

    在学习synchronized时我们知道,一个锁会对应一个等待队列,可以通过wait()notify(),notifyAll()方法来控制线程等待和唤醒。ReentrantLock同样也支持等待和唤醒,不过ReentrantLock可以通过newCondition()来开启多个等待队列,也就是说ReentrantLock一把锁可以绑定多个等待队列。

    condition方法

    • await(): 等待状态,进入等待队列,等待唤醒,与Object的wait()方法功能相同。
    • await(long timeout,TimeUnit unit): 进入有时间的等待状态,被唤醒或等待超时自动唤醒,与Object的wait(long timeout,TimeUnit unit)功能相同。
    • signal(): 唤醒一个等待队列的线程,与notify()功能相同。
    • singlaAll(): 唤醒等待队列中的所有线程,与notifyAll()功能相同。
    • awaitUntil(Date deadline): 进入等待状态,到达指定时间后自动唤醒。
    • awaitUninterruptibly(): 进入不响应线程中断方法的等待状态。
    • awaitNanos(long timeout): 进入纳秒级等待状态,xx纳秒后自动唤醒

    使用Condition

    import org.junit.Test;
    
    import java.time.LocalDateTime;
    import java.time.ZoneId;
    import java.time.ZoneOffset;
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author 赵帅
     * @date 2021/1/14
     */
    public class ConditionTest {
    
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
    
        public void run1() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取锁");
                // 进入等待状态,响应中断,等待唤醒
                condition.await();
                // 进入有超时时间的等待状态,等待结束自动唤醒
                condition.await(1L, TimeUnit.MICROSECONDS);
                // 进入纳秒级等待状态,超时自动唤醒
                condition.awaitNanos(100L);
                // 进入不响应中断的等待状态,无法通过 thread.interrupt() 中断线程
                condition.awaitUninterruptibly();
                // 进入等待状态,指定结束等待的时间,到达时间后自动唤醒
                condition.awaitUntil(Date.from(LocalDateTime.of(2021, 1, 14, 11, 45)
                        .atZone(ZoneId.systemDefault()).toInstant()));
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放锁");
            }
        }
    
        public void run2() {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "获取锁");
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放锁");
            }
        }
    
        @Test
        public void fun1() throws InterruptedException {
            Thread thread1 = new Thread(this::run1, "thread-1");
            Thread thread2 = new Thread(this::run2, "thread-2");
            Thread thread3 = new Thread(this::run2, "thread-3");
    
            thread1.start();
            thread2.start();
            // 等待线程1进入 lock.awaitUninterruptibly()
            TimeUnit.SECONDS.sleep(2L);
            thread3.start();
    
            thread1.join();
            thread2.join();
            thread3.join();
    
        }
    }
    

    Object监视器和Condition的区别

    对比项 Object监视器 condition
    前置条件 synchronized(obj)获取锁 lock.lock()获取锁,lock.newCondition()获取Condition对象
    调用方式 obj.wait() condition.await()
    等待队列个数 1个 多个
    当前线程释放锁进入等待状态 支持 支持
    当前线程释放锁进入超时等待状态 支持 支持
    当前线程释放锁进入等待状态不响应中断 不支持 支持
    当前线程释放锁进入等待状态到将来某个时间 不支持 支持
    唤醒等待队列中的一个线程 支持 支持
    唤醒等待队列中的所有线程 支持 支持

    使用Condition实现阻塞队列BlockingQueue

    import com.sun.org.apache.bcel.internal.generic.NEW;
    
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author 赵帅
     * @date 2021/1/14
     */
    public class BlockingQueue<T> {
    
        private final ReentrantLock lock = new ReentrantLock();
        private final LinkedList<T> list = new LinkedList<>();
        private final Condition notEmpty = lock.newCondition();
        private final Condition notFull = lock.newCondition();
        private final int size;
    
        public BlockingQueue(int size) {
            this.size = size;
        }
    
        /**
         * 入队,如果队列不为空则阻塞。
         */
        public void enqueue(T t) {
            lock.lock();
            try {
                while (list.size() == size) {
                    notFull.await();
                }
                System.out.println("入队:" + t);
                list.add(t);
                notEmpty.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public T dequeue() {
            lock.lock();
            try {
                while (list.size() == 0) {
                    notEmpty.await();
                }
                T t = list.removeFirst();
                System.out.println(Thread.currentThread().getName() + ":出队:" + t);
                notFull.signalAll();
                return t;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            return null;
        }
    
    
        public static void main(String[] args) {
            BlockingQueue<String> queue = new BlockingQueue<>(5);
            // 生产者
            new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    queue.enqueue("str" + i);
                }
            }, "produce-1").start();
    
            // 消费者
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 10; j++) {
                        queue.dequeue();
                    }
                }, "consumer-" + i).start();
            }
        }
    }
    

    使用ReentrantLock来实现输出AbBb...这道题:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author 赵帅
     * @date 2021/1/16
     */
    public class ReentrantLockPrint {
    
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
    
        public void print(char[] array) {
            lock.lock();
            try {
                for (char c : array) {
                    System.out.println(c);
                    condition.signal();
                    condition.await();
                }
                condition.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            char[] array1 = "ABCDEFG".toCharArray();
            char[] array2 = "abcdefg".toCharArray();
    
            ReentrantLockPrint demo = new ReentrantLockPrint();
            new Thread(() -> demo.print(array1)).start();
            new Thread(() -> demo.print(array2)).start();
    
            TimeUnit.SECONDS.sleep(2);
        }
    }
    

    CountDownLatch

    countDownLatch,也叫门栓。使用来等待线程执行完毕的锁。方法如下:

    countDownLatch在创建时需要指定一个count值,表示需要等待完成的线程数。

    • await(): 使当前线程进入等待状态
    • await(long timeout,TimeUnit unit): 使当前线程进入超时等待状态,超时自动唤醒线程。
    • countDown():使count值减1,当count的值为0时,唤醒等待状态的线程。
    • getCount: 获取当前的count值。

    使用如下:

    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 赵帅
     * @date 2020/8/5
     */
    public class CountDownLatchDemo {
    
        private static final CountDownLatch lock = new CountDownLatch(5);
    
        private static void fun1() {
            try {
                System.out.println(Thread.currentThread().getName() + ":到达");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + ":放行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.countDown();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 5; i++) {
                new Thread(CountDownLatchDemo::fun1, "t" + i).start();
                TimeUnit.SECONDS.sleep(2);
            }
            lock.await();
            System.out.println("111");
        }
    }
    

    可以看到,countDownLatch在初始化的时候,必须指定一个大小,这个大小可以理解为加了几道门拴。然后,当一个线程调用await()方法后,那么当前线程就会等待。我们初始化countDownLatch(count)时,指定了一个count,只有当这个count等于0的时候,等待才会结束,否则就会一直等待。每调用一次countDown()方法,那么count值就减1。

    上面的代码我们创建了一个初始化大小为5的countDownLatch,可以为创建了一个门拴,上面有5道锁。然后在main方法中又创建了5个线程,main方法调用了await()方法等待count变成0。然后创建的5个线程,每个线程都会在执行结束时,调用countDown()来将当前的门栓减去1,当所有的线程执行结束,count值变为0。那么main方法就可以继续执行了。

    CountDownLatch主要就是用来等待线程执行结束的。在之前我们都是使用thread.join()方法来将线程加入当前线程来保证线程运行结束,但是这种写法会非常麻烦,如果线程多我们就要写好多遍join。countDownLatch的作用与join方法相同。

    可以理解为:打仗时,有很多个战斗小队,这些战斗小队就是一个个的线程。然后路上遭路雷区,拦住了所有的队伍(调用了await()方法的线程)。这些队伍就停下来,进入等待的状态了。然后就要派人去排雷,每排掉一颗雷。雷的总数就减1。这样当地雷全部排完,队伍才可以继续往下执行。地雷排完之前,队伍都是处于原地等待状态。

    CyclicBarrier

    CyclicBarrier也叫栅栏。与CountDownLatch很相似,都是使线程在这里等待,创建时都需要指定一个int参数parties,表示这个栅栏要拦的线程总数。方法如下:

    • await(): 进入等待状态,并将线程数count加1,当count==parties时,唤醒所有的等待线程。
    • await(long timeout,TimeUnit unit): 进入超时等待状态,超时自动唤醒。
    • getParties(): 获取这个栅栏的大小,即初始化时指定的大小。
    • getNumberWaiting(): 获取目前处于等待状态的线程数。
    • reset(): 重置栅栏,将唤醒所有等待状态的线程。后面来的线程将重新开始计数。

    CyclicBarrier的用法如下:

    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 赵帅
     * @date 2020/8/6
     */
    public class CyclicBarrierDemo {
    
        private static final CyclicBarrier barrier = new CyclicBarrier(5);
    
        public static void fun1() {
            System.out.println(Thread.currentThread().getName() + ":线程到达");
            try {
                barrier.await();
                System.out.println(Thread.currentThread().getName() + ":释放锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(CyclicBarrierDemo::fun1, "t" + i).start();
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    

    CyclicBarrier还可以在创建时指定最后一个线程达到时执行某个方法:

    		private static final CyclicBarrier barrier = new CyclicBarrier(5,()->{
            System.out.println(Thread.currentThread().getName() + ":最后到达");
        });
    

    来看看CyclicBarrier的使用。首先我们创建时也需要指定一个线程数大小,然后还可以指定一个调用函数。创建成功后内部会有一个数维护当前等待的线程数。初始为0,在使用时通过await()方法进入等待时,就将这个数+1,当进入等待状态的线程数与CyclicBarrier指定的线程数相等时就唤醒所有等待的线程,并将等待的线程数清0,开始新一轮的拦截。

    通俗理解就是:打仗时需要撤退,不能乱撤,得听命令,然后就有一个栅栏拦着,所有撤退的人都得等待栅栏打开你才能出。而且撤退都是分批的。不能说一群人一块冲出去,因此就编队。我这个栅栏一次只能出去五个人,人要走前先得来栅栏前面占着位置等着。等凑够5个人了,我就打开你们出去,我等下一批五个人。当创建时指定一个构造方法时,这个构造方法只有最后一个线程到达后会执行,可以理解为:我这个栅栏有一个开关控制,最后一个人过来时,你得先来我这打开开关你才能走。

    CyclicBarrier与CountDownLatch的区别

    • CyclicBarrier是可以重复使用的,当线程数满了后会自动清0。countDownLatch是一次性的,当数减为0后,就失效了。
    • CyclicBarrier可以指定最后一个线程到达时执行一个方法。

    当调用线程中断时会

    Semaphore

    Semaphore又叫信号量,使用方式如下:

    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 赵帅
     * @date 2020/8/6
     */
    public class SemaPhoreDemo {
    
        private static final Semaphore semaphore = new Semaphore(5);
    
        public static void fun1() {
    
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + ":获取令牌");
                TimeUnit.SECONDS.sleep(8);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                semaphore.release();
                System.out.println(Thread.currentThread().getName() + ":释放令牌");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                new Thread(SemaPhoreDemo::fun1, "t" + i).start();
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    

    Samephore理解起来就好像一个房子,门口有保安,一个人想要进去,需要拿一个令牌,领牌的数量是有限的,令牌发完后,后来的人要在门口等着,等里面的人出来会归还领牌,这时等着的人就可以拿领牌进去了。

    因此,samephore比较适合拿来做限流。

    Phaser

    phaser翻译过来是阶段,它维护程序执行的一个阶段一个阶段的。

    import java.util.Random;
    import java.util.concurrent.Phaser;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 赵帅
     * @date 2021/1/16
     */
    public class PhaserDemo {
    
        private static Phaser phaser = new SimplePhaser();
    
        private static class SimplePhaser extends Phaser {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("===============阶段" + phase + "总人数" + registeredParties);
                switch (phase) {
                    case 0:
                        return false;
                    case 1:
                        return false;
                    case 2:
                        return false;
                    case 3:
                        return true;
                    default:
                        return true;
                }
            }
        }
    
        private static class Person implements Runnable {
    
            private final Random random = new Random();
    
            private String name;
    
            public Person(String name) {
                this.name = name;
            }
    
            private void sleepRandom() {
                try {
                    TimeUnit.MILLISECONDS.sleep(random.nextInt(100));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            private void arrive() {
                sleepRandom();
                System.out.println(name + "到达");
                phaser.arriveAndAwaitAdvance();
            }
    
            private void marry() {
                sleepRandom();
                System.out.println(name + "开始结婚");
                phaser.arriveAndAwaitAdvance();
            }
    
            private void eat() {
                sleepRandom();
                System.out.println(name + "开始吃饭");
                phaser.arriveAndAwaitAdvance();
            }
    
            private void leave() {
                sleepRandom();
                if ("新郎".equals(name) || "新娘".equals(name)) {
                    phaser.arriveAndAwaitAdvance();
                } else {
                    System.out.println(name + "吃完饭走");
                    phaser.arriveAndDeregister();
                }
            }
    
            @Override
            public void run() {
                arrive();
    
                marry();
    
                eat();
    
                leave();
            }
        }
    
        public static void main(String[] args) {
    
            phaser.bulkRegister(7);
            for (int i = 0; i < 5; i++) {
                new Thread(new Person("路人" + i)).start();
            }
    
            new Thread(new Person("新郎")).start();
            new Thread(new Person("新娘")).start();
        }
    }
    

    ReadWriteLock

    readwritelock,读写锁,可以拆分为读锁和写锁,读锁时共享锁,写锁时排他锁。用法如下:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * @author 赵帅
     * @date 2020/8/6
     */
    public class ReadWriteLockDemo {
    
        public static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private static final Lock readLock = readWriteLock.readLock();
        private static final Lock writeLock = readWriteLock.writeLock();
    
        /**
         * 读锁
         */
        public static void fun1() {
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + ":获取读锁");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + ":释放读锁");
            }
        }
    
        public static void fun2() {
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + ":获取写锁");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                writeLock.unlock();
                System.out.println(Thread.currentThread().getName() + ":释放写锁");
            }
        }
    
        public static void main(String[] args) {
    
                new Thread(ReadWriteLockDemo::fun1, "t").start();
                new Thread(ReadWriteLockDemo::fun1, "t1").start();
                new Thread(ReadWriteLockDemo::fun1, "t2").start();
                new Thread(ReadWriteLockDemo::fun2, "t3").start();
                new Thread(ReadWriteLockDemo::fun2, "t4").start();
    
        }
    }
    

    LockSupport

    前面的锁都需要在锁内部等待或唤醒,lockSupport支持在锁的外部唤醒指定的锁。相比之下更加灵活,用法如下:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.LockSupport;
    
    /**
     * @author 赵帅
     * @date 2020/8/6
     */
    public class LockSupportDemo {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                    if (i == 5) {
                        LockSupport.park();
                    }
    
                    if (i == 8) {
                        LockSupport.park();
                    }
    
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            thread.start();
            LockSupport.unpark(thread);
            LockSupport.unpark(thread);
        }
    }
    

    LockSupport可以在调用park()方法前先调用unpark()。LockSupport底层使用的是Unsafe类。

    每一个线程都有一个许可(permit),permit的值只有0和1。默认为0。

    当调用unpark(thread)方法时,会将线程的permit值设置为1,多次调用unpark方法,permit的值还是1。

    当调用park()方法时:

    • 如果当前线程的permit的值为1。那么就会将值变为0并立即返回。
    • 如果当前线程的permit的值为0,那么当前线程就会被阻塞,并等待其他线程调用unpark(thread)将线程的值设为1,当前线程将被唤醒。然后会将permit的值设为0,并返回。

    AQS

    AQS全称为AbstractQueuedSynchronizer, ReentrantLock,CountDownLatch等都是通过AQS实现的。

    AQS的核心是state属性,很多实现都是通过操作这个属性来实现的。而且AQS内部的方法都是操作unsafe的CAS操作实现的,因此说AQS的实现都是自旋锁。

    ReentrantLock的实现:

    ReentrantLock内部分为公平锁fairSync和非公平锁NonfairSync,这两个类都继承自Sync,而sync继承AQS, 当调用lock.lock()获取锁时,查看lock()方法的源码:

    				final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
    

    当调用lock方法时,首先会尝试修改state属性的值,从0改成1。如果失败的话,那么就调用acquire方法。acquire方法内部调用了tryAcquire(arg)方法,最终调用NonSyncnonfairTryAcquire(arg)方法,然后内部判断,如果当前的state值是0,那么就尝试将state的值设为1,如果设置1成功,则说明获取到锁,返回true,如果state的值不是0,但是加锁的线程是当前线程,也就是进入可重入锁了,那么就将state的值加1。

    释放锁是,没释放一次就将state的值减1,最终保证state的值是0,那么这把锁就可以被其他线程使用了。

    CountDownLatch的实现:

        public CountDownLatch(int count) {
            if (count < 0) throw new IllegalArgumentException("count < 0");
            this.sync = new Sync(count);
        }
            Sync(int count) {
                setState(count);
            }
    
        public void countDown() {
            sync.releaseShared(1);
        }
    

    CountDownLatch在创建时就讲AQS的state的值设置为count值,然后每次调用countDown方法就将state的值减1,知道state的值变为0.

  • 相关阅读:
    抽象工厂模式
    工厂方法模式
    简单工厂模式
    多例模式
    深入分析 Java 中的中文编码问题
    PipedInputStream和PipedOutputStream详解
    单例模式
    Java IO和NIO文章目录
    wsdlLocation可以写成项目的相对路劲吗
    ssh框架配置事务管理器
  • 原文地址:https://www.cnblogs.com/Zs-book1/p/14287890.html
Copyright © 2011-2022 走看看