JUC-Lock锁及常用工具类

一、Lock简介

在jdk1.5之前实现同步访问一般都是通过synchronized。在Java 5之后,java.util.concurrent.locks包下提供了Lock接口来实现同步访问。锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。

二、Lock与synchronized的区别

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

  • synchronized会自动释放锁,Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

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

  • synchronized的锁可重入、不可中断()、非公平,而Lock锁可重入、可中断、可公平可非公平。

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

  • synchronized只关联一个条件队列,Lock可以关联多个条件队列。

    注意:syschronzied不可中断的意思是等待获取锁的时候不可中断,拿到锁之后可中断,没获取到锁的情况下,中断操作一直不会生效。Lock的lockInterruptibly()可以中断是指在等锁的过程中也可以被中断。

    测试代码如下:

    public class InterruptedDemo{
    	//中断synchronized
        private static void test5() throws InterruptedException {
            Object o1 = new Object();
            Thread thread1 = new Thread(() -> {
                System.out.println("t1 enter");
                synchronized (o1) {
                    try {
                        System.out.println("start lock t1");
                        Thread.sleep(5000);
                        System.out.println("end lock t1");
                    } catch (InterruptedException e) {
                        System.out.println("t1 interruptedException");
                        e.printStackTrace();
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                System.out.println("t2 enter");
                synchronized (o1) {
                    try {
                        System.out.println("start lock t2");
                        Thread.sleep(1000);
                        System.out.println("end lock t2");
                    } catch (InterruptedException e) {
                        System.out.println("t2被中断");
                        e.printStackTrace();
                    }
                }
            });
    
            thread1.start();
            thread2.start();
    
            // 主线程休眠一下,让t1,t2线程百分百已经启动,避免线程交替导致测试结果混淆
            Thread.sleep(1000);
            // 中断t2线程的执行
            thread2.interrupt();
            System.out.println("t2 interrupt...");
    
        }
        //中断lock
        private  static void test6() throws InterruptedException {
            Lock lock = new ReentrantLock();
    
            Thread t1 = new Thread(()->{
                lock.lock();
                try {
                    System.out.println("t6获得锁");
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("t6释放锁");
                    lock.unlock();
                }
            });
            t1.start();
    
            Thread t2 = new Thread(()->{
                System.out.println("t7准备获得锁");
                try {
                    lock.lockInterruptibly();
                    System.out.println("t7获取到锁");
                    System.out.println("t7的中断状态:"+Thread.currentThread().interrupted());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            });
            t2.start();
            Thread.sleep(500);
            System.out.println("中断线程t7");
            t2.interrupt();
        }
        public static void main(String[] args) throws InterruptedException {
            test5();
        }
    }
    

    test5()方法运行结果如下:即使调用了t2线程的中断方法,也要等到t2获取到锁才被中断。

    image-20201022171047251

    test()6方法的运行结果如下:t2线程没有获取到锁,在等锁的过程中就被中断了。

    image-20201022175008855

三、Lock接口的API

void lock() // 如果锁可用就获得锁,如果锁不可用就阻塞直到锁释放
void lockInterruptibly() // 和 lock()方法相似, 但阻塞的线程可中断,抛出 java.lang.InterruptedException异常
boolean tryLock() // 非阻塞获取锁;尝试获取锁,如果成功返回true
boolean tryLock(long timeout, TimeUnit timeUnit) //带有超时时间的获取锁方法
void unlock() // 释放锁
Condition newCondition() //返回一个新Condition绑定到该Lock实例。 

接口中有四个方法使用来获取锁的,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly(),unlock()使用来释放锁的,newCondition()是用来返回返回条件队列的。Lock其实用的基本语法格式如下:

 Lock l = ...;
 l.lock();
 try {
   // access the resource protected by this lock
   //判断,干活,通知  
 } finally {
     //一定要将释放锁的操作写在finally中,因为发生异常时不会自动释放锁
   l.unlock();
 }

四、ReentrantLock可重入锁:

ReentrantLock是Lock接口的实现类,可重入指的是线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次数。获得了几次锁就要释放几次(synchronized也是可重入的)

  • 可重入
//对于main线程即使method1方法没有释放锁,method2方法中同样可以再次获取同一把锁,因为都是main线程。
public class ReentrantLockDemo {
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    public static void method1() {
        lock.lock();
        try {
            System.out.println("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }

    public static void method2() {
        lock.lock();
        try {
            System.out.println("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }

    public static void method3() {
        lock.lock();
        try {
            System.out.println("execute method3");
        } finally {
            lock.unlock();
        }
    }

}

结果:

image-20201009192416872

  • 可以设置超时时间
class ReentrantLockDemo3{
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            System.out.println("启动...");
            try {
                //tryLock()1秒后还获取不到锁直接返回false
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    System.out.println("获取等待 1s 后失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                System.out.println("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println("获得了锁");
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

结果:tryLock()尝试获取锁一秒钟,没有成功获取到锁,返回false,如何没有设置超时时间则立即返回结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XSr3n6DV-1603376107092)(http://img.mcxlblog.cn/qiniu_picGoimage-20201009192641624.png)]

  • 可中断测试上边已有

  • 可以设置公平锁和非公平锁

    RreentrantLock的构造方式可以选择实现公平锁还是非公平锁,默认为非公平锁,公平锁就是按照线程到来的顺序获得锁。非公平锁就是随机获取到锁,和线程的到来顺序无关。公平锁可以防止饥饿现象,但是并发度低。

    //非公平锁
    ReentrantLock lock = new ReentrantLock();
    //公平锁
    ReentrantLock lock2 = new ReentrantLock(true);
    

    在ReentrantLock中定义了2个静态内部类,一个是NotFairSync,一个是FairSync,分别用来实现非公平锁和公平锁。通过构造方法来实现公平锁和非公平锁。true为公平锁,false为非公平锁。

    image-20201022183818751

  • 支持多个条件变量(Condition)

    synchronized 中也有条件变量,当条件不满足时进入 waitSet 等待。而是用notifyAll()唤醒的时候是将整个waitSet中的所有线程全部唤醒.

    ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的(Condition),这就好比有多个waitSet,使用signalAll()唤醒时可以选择某一个条件变量唤醒。

    class ReentrantLockDemo5 {
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock lock = new ReentrantLock();
            Condition condition2 = lock.newCondition();
            Condition condition3 = lock.newCondition();
            Thread t1 = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.lock();
                try {
                    condition2.signalAll();
                    System.out.println("我要去唤醒condition2中等待的线程");
                }finally {
                    lock.unlock();
                }
            });
            t1.start();
            Thread t2 = new Thread(() -> {
                lock.lock();
                try {
                    System.out.println("t2在condition2条件中等待等待");
                    condition2.await();
                    System.out.println("t2被唤醒");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            });
            t2.start();
    
            Thread t3 = new Thread(() -> {
                lock.lock();
                try {
                    System.out.println("t3在condition3条件中等待等待");
                    condition3.await();
                    System.out.println("t3被唤醒");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            });
            t3.start();
        }
    }
    

    结果:只唤醒了t2线程,并没有唤醒t3线程

    image-20201009195548894

五、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();
}

ReentrantReadWriteLock

ReentrantReadWriteLock:重入读写锁,它实现了ReadWriteLock接口,在这个类中维护了两个锁,一个是ReadLock,一个是WriteLock,他们都分别实现了Lock接口。读写锁是一种适合读多写少的场景下解决线程安全问题的工具,基本原则是:读和读不互斥、读和写互斥、写和写互斥。也就是说涉及到影响数据变化的操作都会存在互斥。当读操作远远高于写操作时,这时候使用 读写锁读-读 可以并发,提高性能。可以通过readLock()和writeLock()获取读锁和写锁,

 public ReentrantReadWriteLock.WriteLock writeLock() { 
     return writerLock; 
 }
 public ReentrantReadWriteLock.ReadLock  readLock()  {
     return readerLock; 
 }
  • 读锁读锁可以并发

    public class ReentrantReadWriteLockDemo {
    
        private Object data;
    
        private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        private ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
        private ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    
        public static void main(String[] args) {
            ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
            new Thread(() -> {
                demo.read();
            }, "t1").start();
            new Thread(() -> {
                demo.read();
            }, "t2").start();
        }
    
        public Object read() {
            System.out.println(Thread.currentThread().getName()+"获取读锁...");
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"读取");
                TimeUnit.SECONDS.sleep(1);
                return data;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放读锁...");
                readLock.unlock();
            }
            return null;
        }
    
        public void write() {
            System.out.println(Thread.currentThread().getName()+"获取写锁...");
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"写入");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放写锁...");
                writeLock.unlock();
            }
        }
    }
    

    结果如下:t1线程获取读锁,t2线程同样可以获取读锁

    image-20201022185542675

  • 读锁写锁相互阻塞

    public static void main(String[] args) {
        ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
        new Thread(() -> {
            demo.read();
        }, "t1").start();
        new Thread(() -> {
            demo.write();
        }, "t2").start();
    }

结果如下:t2获取写锁期间t1线程并不能获取到读锁,只有t2释放写锁,t1才能读取

image-20201022190125445

  • 写锁写锁相互阻塞

    public static void main(String[] args) {
            ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
            new Thread(() -> {
                demo.write();
            }, "t1").start();
            new Thread(() -> {
                demo.write();
            }, "t2").start();
        }
    

    结果如下:t1获得写锁期间t2不能写入,只有t1释放写锁后t2才能写入

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-idvN43DY-1603376107099)(http://img.mcxlblog.cn/qiniu_picGoimage-20201022190444116.png)]

  • 读锁不支持条件变量,写锁支持条件变量

    public class ReentrantReadWriteLockDemo {
    
        private Object data;
    
        private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        private ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
        private ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    
        public static void main(String[] args) {
            ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
            new Thread(() -> {
                demo.read();
            }, "t1").start();
            new Thread(() -> {
                demo.write();
            }, "t2").start();
        }
    
        public Object read() {
            System.out.println(Thread.currentThread().getName()+"获取读锁...");
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"读取");
                Condition condition = readLock.newCondition();
                TimeUnit.SECONDS.sleep(1);
                return data;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放读锁...");
                readLock.unlock();
            }
            return null;
        }
    
        public void write() {
            System.out.println(Thread.currentThread().getName()+"获取写锁...");
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"写入");
                Condition condition = writeLock.newCondition();
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"释放写锁...");
                writeLock.unlock();
            }
        }
    }
    

    结果如下:读锁在获取Condition实例时就会报错,而写锁不会报错

    image-20201022191156338

  • 锁重入时持有读锁的情况下去获取写锁,会导致获取写锁永久等待

    public static void main(String[] args) {
            ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
            new Thread(() -> {
                demo.readAndWrite();
            }, "t1").start();
     
        }
     private void readAndWrite() {
            try {
                readLock.lock();
                System.out.println(Thread.currentThread().getName()+"成功获取到读锁!");
                writeLock.lock();
                System.out.println(Thread.currentThread().getName()+"成功获取到写锁!");
            }finally {
                writeLock.unlock();
                System.out.println(Thread.currentThread().getName()+"释放写锁!");
                readLock.unlock();
                System.out.println(Thread.currentThread().getName()+"释放读锁!");
            }
        }
    

    结果如下:t1获取到读锁后,再次获取写锁一直陷入阻塞

    image-20201022192234248

  • 锁重入时持有写锁的情况下可以获取读锁

     public static void main(String[] args) {
            ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
            new Thread(() -> {
                demo.writeAndRead();
            }, "t1").start();
        }
    
    private void writeAndRead() {
            try {
                writeLock.lock();
                System.out.println(Thread.currentThread().getName()+"成功获取到写锁!");
                readLock.lock();
                System.out.println(Thread.currentThread().getName()+"成功获取到读锁!");
            }finally {
                writeLock.unlock();
                System.out.println(Thread.currentThread().getName()+"释放写锁!");
                readLock.unlock();
                System.out.println(Thread.currentThread().getName()+"释放读锁!");
            }
        }
    

    结果如下:t1线程获取到写锁的情况下可以成功的再次获取到读锁

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tJdZoLid-1603376107103)(http://img.mcxlblog.cn/qiniu_picGoimage-20201022192613457.png)]

六、StampedLock

stampedLock是JDK8引入的新的锁机制,可以简单认为是读写锁的一个改进版本,读写锁虽然通过分离读和写的功能使得读和读之间可以完全并发,但是读和写是有冲突的,如果大量的读线程存在,可能会引起写线程的饥饿。StampedLockReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。StampedLock把读分为了悲观读和乐观读,悲观读就等价于ReadWriteLock的读,而乐观读在一个线程写共享变量时,不会被阻塞,乐观读是不加锁的

  • 乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过则表示有写操作修改了共享变量则升级乐观读为悲观读锁。

    public class StampedLockDemo {
        private int data;
        private final StampedLock lock = new StampedLock();
        public StampedLockDemo(int data) {
            this.data = data;
        }
        public int read(int readTime) throws InterruptedException {
            long stamp = lock.tryOptimisticRead();
            System.out.println(Thread.currentThread().getName()+"准备读,stamp = "+stamp);
            TimeUnit.SECONDS.sleep(readTime);
            if (lock.validate(stamp)) {
                System.out.println(Thread.currentThread().getName()+"已经读,stamp = "+stamp);
                return data;
            }
            // 将乐观读升级为悲观读
            System.out.println("乐观读升级为悲观读,stamp = "+stamp);
            try {
                stamp = lock.readLock();
                System.out.println("悲观读,stamp = "+stamp);
                TimeUnit.SECONDS.sleep(readTime);
                System.out.println("悲观读完,stamp = "+stamp+ "  数据data = "+data);
                return data;
            } finally {
                System.out.println("释放读锁,stamp =  "+stamp);
                lock.unlockRead(stamp);
            }
        }
        public void write(int newData) {
            long stamp = lock.writeLock();
            System.out.println("获取写锁,stamp = "+stamp);
            try {
                 TimeUnit.SECONDS.sleep(2);
                this.data = newData;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("释放写锁,stamp = "+stamp);
                lock.unlockWrite(stamp);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            StampedLockDemo stampedLockDemo = new StampedLockDemo(1);
            new Thread(() -> {
                try {
                    stampedLockDemo.read(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t1").start();
    
            TimeUnit.SECONDS.sleep(1);
    
            new Thread(() -> {
                try {
                    stampedLockDemo.read(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t2").start();
        }
    }
    
    

    结果如下:乐观读实际并没有加锁,只是验证stamp

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JelR14mg-1603376107105)(http://img.mcxlblog.cn/qiniu_picGoimage-20201022201355978.png)]

  • 悲观读情况演示

     public static void main(String[] args) throws InterruptedException {
            StampedLockDemo stampedLockDemo = new StampedLockDemo(1);
            new Thread(() -> {
                try {
                    stampedLockDemo.read(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t1").start();
    
            TimeUnit.SECONDS.sleep(1);
    
            new Thread(() -> {
                stampedLockDemo.write(3);
            }, "t2").start();
        }
    

    结果如下:由于t1线程在乐观读的时候,t2线程修改了数据,导致t1线程验证stamp时与获取到的stamp不一致,此时t1线程由乐观读升级为悲观读。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7nYQ9c2U-1603376107106)(http://img.mcxlblog.cn/qiniu_picGoimage-20201022201857859.png)]

    注意

    StampedLock不是可重入锁,所以不支持重入,并且StampedLock不支持条件变量,也就是没Condition

七、Semaphore

Semaphore也叫信号量,在JDK1.5被引入,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

Semaphore内部维护了一组虚拟的许可,许可的数量可以通过构造函数的参数指定。

  • 访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可。
  • 访问资源后,使用release释放许可。

测试代码如下:

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 1. 创建 semaphore 对象
        Semaphore semaphore = new Semaphore(3);
        // 2. 10个线程同时运行
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"获取许可!");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 4. 释放许可
                    System.out.println(Thread.currentThread().getName()+"释放许可!");
                    semaphore.release();
                }
            }).start();
        }
    }
}

结果如下:每个线程必需要获得许可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i3BqEtfl-1603376107107)(http://img.mcxlblog.cn/qiniu_picGoimage-20201022204007388.png)]

八、CountdownLatch

CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行或者等待时间超时。其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一,计数器count是闭锁需要等待的线程数量,只能被设置一次,且CountDownLatch没有提供任何机制去重新设置计数器count。CountDownLatch可以用来替代join();

CountdownLatch的所有方法如下,见名知意,很容易理解:

image-20201022205217860

测试代码如下:

public class CountdownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"开始...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            latch.countDown();
            System.out.println(Thread.currentThread().getName()+"结束...  ,count剩余数量 = "+ latch.getCount());
        }).start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"开始...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            latch.countDown();
            System.out.println(Thread.currentThread().getName()+"结束...  ,count剩余数量 = "+ latch.getCount());
        }).start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"开始...");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            latch.countDown();
            System.out.println(Thread.currentThread().getName()+"结束...  ,count剩余数量 = "+ latch.getCount());
        }).start();
        System.out.println("waiting...");
        latch.await();
        System.out.println("wait end...");
    }
}

结果如下:主线程调用CountDownLatch.await()方法后直到CountDownLatch的count减为0后,才开始执行。

image-20201022205914843

如果设置超时时间

 latch.await(2,TimeUnit.SECONDS);

结果如下:即使count没有减为0但是已经等待超时,主线程也恢复了运行

image-20201022210056593

九、CyclicBarrier

CyclicBarrier循环栅栏也叫同步屏障,在JDK1.5被引入,可以让一组线程达到一个屏障时被阻塞,直到最后一个线程达到屏障时,所有被阻塞的线程才能继续执行。 CyclicBarrier好比一扇门,默认情况下关闭状态,堵住了线程执行的道路,直到所有线程都就位,门才打开,让所有线程一起通过。

构造方法

  1. 默认的构造方法是CyclicBarrier(int parties),其参数表示要拦截的线程的数量,每个线程调用await方法告诉CyclicBarrier已经到达屏障位置,线程被阻塞。

  2. 另外一个构造方法CyclicBarrier(int parties, Runnable barrierAction),其中barrierAction任务会在所有线程到达屏障后执行。

    测试代码如下:

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(2,()->{
            System.out.println("所有线程都已就位!!!");
        }); // 个数为2时才会继续执行
        new Thread(()->{
            System.out.println("线程1开始.."+new Date());
            try {
                System.out.println(Thread.currentThread().getName()+"到达屏障");
                cb.await(); // 当个数不足时,等待
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("线程1继续向下运行..."+new Date());
        }).start();
        new Thread(()->{
            System.out.println("线程2开始.."+new Date());
            try {
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+"到达屏障");
                cb.await(); // 2 秒后,线程个数够2,继续运行
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("线程2继续向下运行..."+new Date());
        }).start();
    }
}

结果如下:只有到达栅栏的线程达到指定个数,才行继续运行,barrierAction线程也在这个时候才开始执行

image-20201022211802522

如果设置超时时间且有线程没有在超时时间之内到达栅栏:

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(2,()->{
            System.out.println("所有线程都已就位!!!");
        }); // 个数为2时才会继续执行
        new Thread(()->{
            System.out.println("线程"+Thread.currentThread().getName()+"开始.."+new Date());
            try {
                System.out.println(Thread.currentThread().getName()+"到达屏障");
                cb.await(1, TimeUnit.SECONDS); // 当个数不足时,等待
            } catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"继续向下运行..."+new Date());
        }).start();
        new Thread(()->{
            System.out.println("线程"+Thread.currentThread().getName()+"开始.."+new Date());
            try {
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+"到达屏障");
                cb.await(); // 2 秒后,线程个数够2,继续运行
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"继续向下运行..."+new Date());
             System.out.println("栅栏状态"+cb.isBroken());
        }).start();

    }
}

结果如下:由于线程0先到达栅栏且设置了超时时间,线程1没能在线程1的超时时间内到达,此时线程1会抛出超时异常,当线程1到达时(即调用await()方法)时会抛出栅栏破碎异常,此时栅栏已经被破坏。

image-20201022214208755

CyclicBarrier和CountDownLatch的区别

  • CountDownLatch 是一次性使用的,CyclicBarrier 是可循环利用的
  • CyclicBarrier 类似于人满发车,CountDownLatch 类似于人走了才发车
原文地址:https://www.cnblogs.com/myblogstart/p/13861395.html