同步和协作工具类
一、读写锁ReentrantReadWriteLock
ReadWriteLock接口的定义为:
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }
读操作使用读锁,写操作使用写锁。只有"读-读"操作是可以并行的,"读-写"和"写-写"都不行。
始终只有一个线程能进行写操作,在获取写锁时,只有没有任何线程持有任何锁才可以获取到,
在持有写锁时,其他任何线程都获取不到任何锁。在没有其他线程持有写锁的情况下,多个线程可以获取和持有读锁。
ReentrantReadWriteLock的两个构造方法:
public ReentrantReadWriteLock() { this(false); }
public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); }
其中fair表示是否公平。
二、信号量Semaphore
信号量类Semaphore类用来限制对资源并发访问的线程数,构造方法:
//permits表示许可数量 public Semaphore(int permits) //fair表示是否公平 public Semaphore(int permits, boolean fair)
Semaphore方法与锁类似,主要有两类方法,获取许可和释放许可:
//阻塞获取许可 public void acquire() throws InterruptedException //阻塞获取许可,不响应中断 public void acquireUninterruptibly() //批量获取多个许可 public void acquire(int permits) throws InterruptedException public void acquireUninterruptibly(int permits) //尝试获取 public boolean tryAcquire() //限定等待时间获取 public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException //释放许可 public void release()
限制并发访问数量不超过100的例子:
public class AccessControlService { public static class ConcurrentLimitException extends RuntimeException { } private static final int MAX_PERMITS = 100; private Semaphore permits = new Semaphore(MAX_PERMITS, true); public boolean login(String name, String password) { //每次acquire都会消耗一个许可 if (!permits.tryAcquire()) { throw new ConcurrentLimitException(); } return true; } public void logout(String name) { permits.release(); } }
Semaphore permits = new Semaphore(1); permits.acquire(); //程序会阻塞在第二个acquire调用 permits.acquire(); System.out.println("acquired");
信号量也是基于AQS实现的。
三、倒计时门栓CountDownLatch
用于需要线程同步的情景。该类相当于一个门栓,一开始是关闭的,所有希望通过该门的线程都需要等待,
然后开始倒计时,倒计时变为0的时候,门栓打开,所有线程通过,它是一次性的,打开后不能关闭。构造函数:
public CountDownLatch(int count)
与多个线程的协作方法:
//检查计数是否为0如果大于0就等待。await可以被中断,也可以设置最长等待时间 public void await() throws InterruptedException public boolean await(long timeout, TimeUnit unit) throws InterruptedException //countDown检查计数,如果已经为0,直接返回,否则减少计数,如果新的计数变为0, //则唤醒所有线程 public void countDown()
public class RacerWithCountDownLatch { static class Racer extends Thread { CountDownLatch latch; public Racer(CountDownLatch latch) { this.latch = latch; } @Override public void run() { try{ this.latch.await(); System.out.println(getName() + " start run " + System.currentTimeMillis()); } catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { int num = 10; CountDownLatch latch = new CountDownLatch(1); Thread[] threads = new Thread[num]; for (int i = 0; i < num; i++) { threads[i] = new Racer(latch); threads[i].start(); } Thread.sleep(1000); latch.countDown(); /*Thread-0 start run 1545714108398 Thread-3 start run 1545714108398 Thread-4 start run 1545714108398 Thread-5 start run 1545714108398 Thread-6 start run 1545714108398 Thread-7 start run 1545714108399 Thread-8 start run 1545714108399 Thread-9 start run 1545714108399*/ } }
四、循环栅栏CyclicBarrier
所有线程在到达栅栏后都需要等待其他线程,等所有线程都到达后再一起通过,
它是循环的,可以用作重复的同步。构造方法:
//parties参与线程个数 public CyclicBarrier(int parties)
//barrierAction表示所有线程到达栅栏后,所有线程执行下一步动作前, //运行参数中的动作,这个动作由最后一个到达栅栏的线程执行 public CyclicBarrier(int parties, Runnable barrierAction)
主要方法:
//等待其他线程到达栅栏,调用await后表示自己已经到达,如果是最后一个到达的,就执行可选命令,执行完毕后,唤醒所有等待的线程,然后重置内部的同步计数 public int await() throws InterruptedException, BrokenBarrierException public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
注意:在CyclicBarrier中,参与的线程是互相影响的,只要有其中的一个线程在调用await时被中断或者超时了,
栅栏就会被破坏。此外,如果栅栏动作抛出了异常,栅栏也会被破坏。被破坏后,所有在调用的await线程就会退出,
抛出BrokenBarrierException。
五、ThreadLocal
1.基本概念和用法
线程本地变量:每个线程都有同一个变量的独特拷贝。ThreadLocal是一个泛型类,
接受一个类型参数T,它只有一个空的构造方法,有两个主要的public方法:
//获取值 public T get() //设置值 public void set(T value)
public class ThreadLocalBasic { static ThreadLocal<Integer> local = new ThreadLocal<Integer>(); public static void main(String[] args) throws InterruptedException { Thread child = new Thread(){ @Override public void run() { System.out.println("child thread initial " + local.get()); //null local.set(200); System.out.println("child thread final: " + local.get()); //200 } }; local.set(100); child.start(); child.join(); System.out.println("Main thread final : " + local.get()); //100 } }
从上面的例子可以看出,一个线程本地变量,在每个线程都有自己的独立值。
ThreadLocal的其他方法:
//用于提供初始值 protected T initialValue() //删除当前线程的对应值,删掉后再次调用get就会获取初始值 public void remove()
应用:是实现线程安全、减少竞争的一种方案。