并行程序设计模式

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)
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对象为协程间的通信载体,用于数据共享和信息交流。