并行程序设计模式
Future模式,调用方式改为异步。
public static void main(String[] args){ Client client=new Client(); Data data=client.request("name");//这里会立即返回,因为得到的是FutureData,而不是RealData System.out.println("请求完毕"); try{ Thread.sleep(2000);//模拟耗时处理其他业务操作 }catch(InterruptedException e){ } System.out.println(data.getResult());//使用真实数据 }
public class Client{ public Data request(final String queryStr){ new Thread(){ public void run(){ RealData readdata=new RealData(queryStr); future.setRealData(realdata); } }.start(); return future; } }
public interface Data{ public String getResult(); } public class FutureDataa implements Data{ protected RealData realdata=null; protected boolean isReady=false; public synchronized void setRealData(RealData realdata){ if(isReady){ return; } this.realData=realdata; isReady=true; notifyAll(); } public synchronized String getResult(){ while(!isReady){ try{ wait(); //一直等待,知道RealData被注入 }catch(InterruptedException e){} } return realdata.result; } }
Master-Worker模式
GuardedSuspension模式
不变模式
生产者-消费者模式
JDK多任务执行框架
无限制线程的缺陷
对线程的使用必须掌握一个度,在有限的范围内增加线程的数量可以提高系统性能,但超出这个范围大量的线程只会拖垮应用系统,因此在生产环境中使用线程,必须对其加以控制和管理。
简单的线程池:
可以减少平凡啊被调度,复用线程,对提高性能是非常有帮助的。
Executor框架:
自定义线程池:
优化线程池大小:
JDK并发数据结构
并发List
多线程环境中尽量避免使用ArrayList,因为线程不安全,尽量使用Vector和CopyOnWriteArrayList
并发Set
CopyOnWriteArraySet内部实现完全依赖CopyOnWriteArrayList适用于读多写少的高并发场合
并发Map
JDK提供了一个高并发的Map实现,ConcurrentHashMap
并发Queue
并发队列上两套实现:ConcurrentLinkedQueue代表高性能队列
BlockingQueue接口为代表的阻塞队列。
并发Duque
双端队列,允许在队列的头部或者尾部进行出队和入队操作,
并发控制方法
常用的方法有:内部所、重入锁、读写锁、信号量等
Java内存模型与volatile
- 声明为volatile的变量,当啊其他线程对变量的修改会即时地反应在当前线程中
- 确保当前线程对volatile变量的修改,能即时写回共享内存中,并被其他线程所见
- 使用volatile声明的变量,编译器会保证其有序性
同步关键字synchronized
ReentrantLock重入锁
- 公平锁保证在锁的等待队列中各个线程是公平的,不存在插队情况,对锁的获取总是先进先出。
- 非公平锁申请锁的先出可能插队,非公平锁的性能要好得多。因此若无特殊需要,应该优先选择非公平锁。
- 使用重入锁,要做程序最后释放锁,一般写在finally里,否则程序出现异常,重入锁就无法释放了。
ReadWriteLock读写锁
读写分离锁有效地帮助减少了锁的竞争,以提升系统的性能。允许多个线程同时读,但写写操作,读写操作相互之间需要相互等待和持有锁的。
Condition对象
用于协调多线程间的复杂协作,通过Lock接口的Condition newCondition()方法可以生成一个与锁绑定的Condition实例,Condition对象与锁的关系如同Object.wait()、Object.nitify()两个函数以及synchronized关键字一样,它们都可以配合使用对线程协作的控制。
Semaphore信号量
信号量为多线程协作提供了更为强大的控制方法,信号量是对锁的扩展。
对于内部锁或者重入锁,一次只允许一个线程访问一个资源,但信号量可以指定多个线程同时访问某一个资源。
ThreadLocal线程局部变量
多线程间并发访问变量的解决方案,完全不提供锁,而使用以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全。
锁的性能和优化
高并发环境中,激烈的锁竞争会导致性能下降,处理系统本身的处理功能,还需要额外维护多线程环境特有的信息(线程调度、上下文切换等)。
避免死锁
条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而被阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
只要破坏其中任何一个,死锁问题迎刃而解。
减少锁的持有时间:
public synchronized void syncMethod(){ othercode1(); mutextMethod(); //只有改方法有同步需求 othercode2(); } //优化代码如下: public void synchMethod2(){ othercode1(); synchronized(this){ mutexMethod(); //只对有需求的方法同步 } othercode2(); }减少锁粒度:
JDK并发包的重要成员ConcurrentHashMap类,使用拆分锁对象的方法将整个HashMap分成若干个段(Segment),每个子段都是一个子HashMap。
默认情况下ConcurrentHashMap拥有16个段,因此可以接受16个线程同时插入(如果都插入不同的段中)从而大大提高吞吐量。
所谓减少锁粒度,就是指缩小锁定对象的范围,从而减少锁冲突的可能性,提高系统的并发能力。
读写分离锁来替换独占锁:
减少锁粒度是分割数据结构,读写分离锁是对系统功能点的分割。
使用读锁,在读读操作中不需要互相等待,读锁之间相容,对象同一时刻可以持有多个读锁。
使用写锁,是独占式的,只有在对象没有锁的情况下才能获得对象的写锁,在写锁释放前也无法在此对象上附加任何锁。
LinkedBlockingQueue基于链表的fake()和put()操作,两个操作分别作用于队列的前端和尾端,JDK中没有使用独占锁,而是所分离。
private final ReentrantLock takeLock=new ReentrantLock(); //take()函数需要持有takeLock private final Condition notEmpty=takeLock.newCondition(); private final ReentrantLock putLock=new ReentrantLock(); //put()函数需要持有putLock private final Condition notFull=putLock.newCondition();重入锁和内部锁(ReentrantLock,synchronized)
重入锁必须在finally代码库中显示释放重入锁,而内部锁可以自动释放。
重入锁提供了锁等待时间(boolean tryLock(long time,TimeUnit unit)),支持锁中断(lockInterruptibly),和快速锁轮询(boolean tryLock())
同时,重入锁还提供了一套Condition机制,可以进行线程控制功能,内部锁需要Object的wait()和notify()
锁粗化(Lock Coarsening)
在遇到连续对同一锁不断请求和释放的操作,便会把锁整合成对锁的一次请求,从而减少对锁的请求同步次数。
public void demoMethod(){ synchronized(lock){ //do sth. } } synchronized(lock){ //do sth. //做其他不需要同步的工作,但很快执行完毕 } //整合后 public void demoMethod(){ //整合锁 synchronized(lock){ //do sth. //做其他不需要的同步工作,但能和快执行完毕 } }自旋锁(Spinning Lock)
JVM引入了自旋锁,可以使线程杂爱没有取得锁时,不被挂起,而转而去执行一个空循环,在若干个空循环后,线程如果获得了锁,则继续执行,若线程依然不能获得锁,才会被挂起。
锁消除(Lock Elimination)
锁消除(Lock Elimination)
JVM在即时编译时,通过对运行上下文的扫描去除不可能存在共享资源竞争的锁,通过锁清除,可以节省毫无意义的请求锁时间。
锁偏向(Biased Lock):在JVM中使用-XX:+UseBiasedLocking
无锁的并行计算
非阻塞同步的方法,不需要使用锁,但依然能够确保数据在高并发环境下的一致性。
非阻塞的同步/无锁
ThreadLocal为代表,每个线程拥有各自独立的变量副本,在并行计算时无需相互等待。
CAS(V,E,N)算法:仅当需要更新的变量V等于预期值E时,才会用N来更新。
原子操作
JDK中java.util.concurrent.atomic包下,无锁算法实现的原子操作类。
AtomicInteger AtomicIntegerArray AtomicLong AtomicLongArray AtomicReference
分别封装了对整数、整数数组、长整型、长整型数组和普通对象的对线程安全操作。
public final int get(); //取得当前值 public final void set(int newValue) //设置当前值 public final int getAndSet(int newValue) //设置新值并返回旧值
协程
协程是对线程的进一步分割。
Kilim框架:
Task是线程的任务载体,其中execute方法用于执行一段任务代码。
Fiber对象用于保存和管理任务的执行堆栈,以实现任务可以正常暂停和继续。
Mailbox对象为协程间的通信载体,用于数据共享和信息交流。