Concurrent包工具类使用
一。读写锁
传统的同步锁就是独占式锁,当线程使用资源时候保持独占,无论读写。当人们发现请求队列(假设)中相邻请求为读-读的时候,阻塞是一种浪费资源的操作。比如公告板,所有路过的人(请求)都是读操作,并没有因为你和他在读的时候对内容造成了改变,所以在模型中,读与读操作不需要阻塞。而读写相邻则需要进行独占式操作了,因为写未完成的时候,信息是不完整的,此时读出来的信息有可能是错误的,所以写必然要保持独占式操作。而在应用程序中,读的频率是写的好几倍,也就是说如果读-读是不阻塞的,那么对性能来说是毋庸置疑的提升。
Java中存在一种锁,名曰:ReentrantReadWriteLock。他可以实现内存中对资源操作的读写锁,读与读是不阻塞的。
import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Created by MacBook on 2018/3/10. */ public class ReadWriteLockDemo { private static Lock relock = new ReentrantLock(); private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private static Lock readLock = readWriteLock.readLock(); private static Lock writeLock = readWriteLock.writeLock(); private int value; public Object handleRead(Lock lock) throws Exception{ try{ lock.lock(); Thread.sleep(1000); return value; }finally { lock.unlock(); } } public void handleWrite(Lock lock,int index) throws Exception{ try{ lock.lock(); Thread.sleep(1000); value = index; }finally { lock.unlock(); } } public static void main(String[] args){ ReadWriteLockDemo demo = new ReadWriteLockDemo(); Runnable readThread = new Runnable() { @Override public void run() { try{ System.out.println("read:"+demo.handleRead(readLock)); }catch (Exception e){ e.printStackTrace(); } } }; Runnable writeThread = new Runnable() { @Override public void run() { try{ // demo.handleWrite(relock,new Random().nextInt()); demo.handleWrite(writeLock,new Random().nextInt()); System.out.println("id:"+Thread.currentThread().getId()+" done!"); }catch (Exception e){ e.printStackTrace(); } } }; for(int i=0;i<18;i++){ new Thread(readThread).start(); } for(int i=0;i<18;i++){ new Thread(writeThread).start(); } } }
此demo使用了重入锁和读写锁的对比,在主程序中分别新建18个读写操作,如果使用了读操作,则打印的读操作是连续的;如果使用了重入锁,则可能的情况是读写相邻打印,并且都是阻塞的,读者可以自行测试体会。
二。对象监视器Condition
在JDK实现了Lock来简化synchronized之后,Condition作为简化监视器而存在。Condition的await方法和signal方法对应对象的wait和signal。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * Created by MacBook on 2018/3/10. */ public class ConditionAndLock implements Runnable{ static ReentrantLock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public void run(){ try{ lock.lock(); condition.await(); System.out.println("thread is running"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public static void main(String[] args){ ConditionAndLock c = new ConditionAndLock(); Thread t = new Thread(c); t.start(); lock.lock(); System.out.println("signal all"); condition.signalAll(); lock.unlock(); } }
三。倒计时器CountDownLatch
多线程中,需要知道这批线程的最大完成任务时间,也就是从第一个任务开始到最后返回这段时间的时长,那么倒计时器是必不可少的。就像各项资源准备完毕才进行下一步操作的模型一样,CountDownLatch就是这样的多线程模型。等到所有任务调用了计数器,并且计数器总数到达某个数量时候,它才会将阻塞代码放开,让主线程往下走。
import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 倒计时器 * Created by MacBook on 2018/3/10. */ public class CountDownLatchDemo implements Runnable{ static CountDownLatch end = new CountDownLatch(10); static CountDownLatchDemo demo = new CountDownLatchDemo(); public void run(){ try{ Thread.sleep(new Random().nextInt(10)*1000); System.out.println(Thread.currentThread().getId()+" check complete!"); end.countDown(); }catch (Exception e){ e.printStackTrace(); } } public static void main(String[] args) throws Exception{ ExecutorService service = Executors.newFixedThreadPool(10); for(int i=0;i<10;i++){ service.submit(demo); } end.await(); System.out.println("fire"); service.shutdown(); } }
await方法是阻塞倒计时器所在线程的方法,等到线程池service中调用countDown方法到达一定的数量(此处是10)之后,主线程的await方法才会过去。
四。信号量
信号量这个东西就比较玄乎了,有点像准入许可,拿到信号准入的时候才往下执行。就像是有一批人拿号,只有号码区间在某个范围的人能进去办事,然后办完事就会让资源释放,号码区间往后移。然而在信号量中应该算是复用类型的,归还了key值,将key值返回给下一个申请者。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * Created by MacBook on 2018/3/10. */ public class SemapDemo implements Runnable{ final Semaphore semp = new Semaphore(5); public void run(){ try{ semp.acquire(); Thread.sleep(2000); System.out.println(Thread.currentThread().getId()+" done!"); semp.release(); }catch (Exception e){ e.printStackTrace(); } } public static void main(String[] args){ ExecutorService executorService = Executors.newFixedThreadPool(5); SemapDemo semapDemo = new SemapDemo(); for(int i=0;i<20;i++){ executorService.submit(semapDemo); } executorService.shutdown(); } }
在acquire获得key之后,操作读写,之后release。
五。栅栏
栅栏和倒计时器很像,就是拦住一堆线程,等到线程数达到某个设定值之后同时把它们放出去。但是不同的是,它可以每次设定值达成时候运行定制线程中的run方法。就像是每次一个栏,够数就放。
import java.util.Random; import java.util.concurrent.CyclicBarrier; /** * Created by MacBook on 2018/3/10. */ public class CylicBarrierDemo { public static class Soldier implements Runnable{ private String soldier; private final CyclicBarrier cyclicBarrier; Soldier(String soldier,CyclicBarrier cyclicBarrier){ this.soldier = soldier; this.cyclicBarrier = cyclicBarrier; } public void run(){ try{ cyclicBarrier.await(); doWork(); cyclicBarrier.await(); }catch (Exception e){ e.printStackTrace(); } } public void doWork(){ try{ Thread.sleep(Math.abs(new Random().nextInt()%10000)); }catch (Exception e){ e.printStackTrace(); } System.out.println(soldier + " done!"); } } public static class BarrierRun implements Runnable{ boolean flag; int n; public BarrierRun(boolean flag,int n){ this.flag = flag; this.n = n; } public void run(){ if(flag){ System.out.println("士兵:"+n+"个 done!"); }else { System.out.println("士兵:"+n+"个 集合完毕!"); flag = true; } } } public static void main(String[] args){ final int n = 10; Thread[] allSoldier = new Thread[n]; boolean flag = false; CyclicBarrier cyclic = new CyclicBarrier(n,new BarrierRun(flag,n)); System.out.println("集合"); for(int i =0; i < n ; i++){ System.out.println("士兵 "+i+" 报道"); allSoldier[i] = new Thread(new Soldier("士兵"+i,cyclic)); allSoldier[i].start(); } } }
例中CyclicBarrier有两个参数,前一个就是提到的设定值,后一个就是定制线程了。每当到达设定值的时候会触发定制线程。
每个阶段完成都会调用一下定制线程。
六。LockSupport提供线程挂起操作的支持类
正如Condition使得原有的Object监视器封装成了新类,LockSupport提供使线程park和unpark之类的操作。
import java.util.concurrent.locks.LockSupport; /** * Created by MacBook on 2018/3/10. */ public class LockSupportDemo { public static Object u = new Object(); static ChangeObjectThread t1 = new ChangeObjectThread("t1"); static ChangeObjectThread t2 = new ChangeObjectThread("t2"); public static class ChangeObjectThread extends Thread{ public ChangeObjectThread(String name){ super.setName(name); } public void run(){ synchronized (u){ System.out.println("in "+getName()); LockSupport.park(); } } } public static void main(String[] args) throws Exception{ t1.start(); Thread.sleep(100); t2.start(); LockSupport.unpark(t1); LockSupport.unpark(t2); t1.join(); t2.join(); } }
它在park时候线程会变成wait状态,而不是runnable。
来自《Java高并发程序设计》的读书笔记