并行模式与算法

单例模式

用来产生一个对象的具体实例

饿汉模式,不能确保单例在什么时候创建

public class Singleton {
	private Singleton(){}
	private static Singleton uniqueSingleton = new Singleton();
	public static Singleton getInstance(){
		return uniqueSingleton;
	}
}

饿汉模式,静态初始化时就创建了,保证了线程安全

多线程会出现问题,可以在方法上加synchronized,但会大大影响性能

public class Singleton {
	private static Singleton uniqueSingleton;
	private Singleton(){}
	public static synchronized Singleton getInstance(){
		if(uniqueSingleton==null){//用getInstance()去实例化对象,并返回这个实例
			uniqueSingleton= new Singleton();
		}//如果实例不存在就会创建,不需要这个实例就永远不会创建(延迟实例化)
		return uniqueSingleton;		
	}
}

懒汉模式,双重检查枷锁,据说不好

public class Singleton {
	private volatile static Singleton unqiueInstance;//voiatile关键字确保,当uniqueInstance被初始化成Singleton实例时,多个线程正确的处理unqiueInstance
	private Singleton(){}
	public static Singleton getInstance(){
		if(unqiueInstance==null){//检查实例,如果不存在就进入同步区
			synchronized (Singleton.class) {//只有第一次才彻底执行这一块的代码
				if(unqiueInstance==null){//再检查一次,如果仍是null,才创建
					unqiueInstance = new Singleton();
				}			
			}
		}
		return unqiueInstance;
	}	
}
没有锁,性能优越,延迟加载

public class Singleton {
	private Singleton(){}
	public static  class SingletonHolder{
		private static Singleton instance = new Singleton();
	}
	//getInstance()第一次被调用,Singleton实例才会被创建
	public static Singleton getInstance(){
		return SingletonHolder.instance;
	}
}
不变模式
一个对象被创建后,内部状态永远不会发生改变

用在:对象创建后,内部状态和数据不再发生变化;对象需要被共享,被多线程频繁访问

实现:去除所有可以修改自身的方法;所有属性设为私有的,加final,确保不可修改;确保没有子类重载;有一个可以创建完整对象的构造函数

public final class Product {//确保无子类
	private final String no;//私有属性,不会被其他对象获取
	private final String name;//final属性不会被二次赋值
	private final double price;
	public Product(String no, String name, double price) {//创建对象时,必须赋值,创建后,无法修改
		super();
		this.no = no;
		this.name = name;
		this.price = price;
	}
	public String getNo() {
		return no;
	}
	public String getName() {
		return name;
	}
	public double getPrice() {
		return price;
	}	
}
元素的包装类使用不变模式

生产者消费者模式

public final class PCDate {//任务相关的数据
	private final int intDate;//数据
	public PCDate(int d) {
		this.intDate = d;
	}
	public PCDate(String d){
		this.intDate = Integer.valueOf(d);
	}
	public int getIntDate() {
		return intDate;
	}
	@Override
	public String toString() {
		return "PCDate [intDate=" + intDate + "]";
	}
}
	public class Producer implements Runnable{
		private volatile boolean isRunning = true;
		private BlockingQueue<PCDate> queue;//内存缓冲区
		private static AtomicInteger count = new AtomicInteger();//总数,原子操作	
		private static final int SLEEPTIME = 1000;
		public Producer(BlockingQueue<PCDate> queue) {
			this.queue = queue;
		}
		@Override
		public void run() {
			PCDate date = null;
			Random r = new Random();
			System.out.println("start productor id="+Thread.currentThread().getId());
			try {
				while(isRunning){
					Thread.sleep(r.nextInt(SLEEPTIME));
					date = new PCDate(count.incrementAndGet());//构造任务数据
					System.out.println(date+" is put into queue");
					if(!queue.offer(date, 2, TimeUnit.SECONDS)){//提交数据到缓冲区
						System.err.println("failed to put date:"+date);
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
				Thread.currentThread().interrupt();
			}			
		}
		public void stop(){
			isRunning = false;
		}
	}
public class Consumer implements Runnable {
	private BlockingQueue<PCDate> queue;//内存缓冲区
	private static final int SLEEPTIME = 1000;
	public Consumer(BlockingQueue<PCDate> queue) {
		this.queue = queue;
	}
	@Override
	public void run() {
		System.out.println("start consumer id="+Thread.currentThread().getId());
		Random r = new Random();
		try {
			while(true){
				PCDate data = queue.take();//提取任务
				if(null !=data){
					int re = data.getIntDate()* data.getIntDate();//计算平方
					System.out.println(MessageFormat.format("{0}*{1}={2}", data.getIntDate(),data.getIntDate(),re));
				}
				Thread.sleep(r.nextInt(SLEEPTIME));
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}			
	}
}
停止生产者失败

因为有锁,效率慢,可以使用CAS无锁,但很复杂
无锁的缓存框架Disruptor


内部实现为一个普通的数组,需要默认设置数组大小,提供当前位置cursor,为了快速从一个序列对应到数组的实际位置,每次有元素入队,序列就加1,必须将数组大小设置为2的整数次方,通过sequence&(queueSize-1)能快速定位到实际元素位置,2的幂次方,表示二进制为10,100,1000等,queueSize-1必为全1的数字,可以将sequence限定在queueSize-1范围内,不会有浪费

public class PCDate {//任务相关的数据
	private long value;//数据
	public long getValue() {
		return value;
	}
	public void setValue(long value) {
		this.value = value;
	}
}
//构建所有的缓冲区中的对象实例
public class PCDataFactory implements EventFactory<PCDate>{
	@Override
	public PCDate newInstance() {
		return new PCDate();
	}	
}
	public class Producer{
		private final RingBuffer<PCDate> ringBuffer;//环形缓冲区的引用

		public Producer(RingBuffer<PCDate> ringBuffer) {
			this.ringBuffer = ringBuffer;
		}
		public void pushDate(ByteBuffer bb){//将产生的数据推入缓冲区
			long sequence = ringBuffer.next();//得到下一个可用的序列号
			try {				
				PCDate event = ringBuffer.get(sequence);//获得下一个PCDdata
				event.setValue(bb.getLong(0));//设为期望值
			} finally {
				ringBuffer.publish(sequence);//数据发布
			}
		}
	}
//读取数据进行处理
public class Consumer implements WorkHandler<PCDate> {
	//读取已经封装
	@Override
	public void onEvent(PCDate event) throws Exception {
		System.out.println(Thread.currentThread().getId()+":Event:--"+event.getValue()*event.getValue()+"--");
		
	}
}
public class Main {
	public static void main(String []args) throws InterruptedException{		
		ExecutorService service = Executors.newCachedThreadPool();
		PCDataFactory factory = new PCDataFactory();
		int bufferSize = 1024;//设置缓冲区大小为2的幂次方
		//创建了disruptor对象
		Disruptor<PCDate> disruptor = new Disruptor<>(factory, bufferSize, service,ProducerType.MULTI,new BlockingWaitStrategy());
		//设置消费者,将每个消费者映射到一个线程
		disruptor.handleEventsWithWorkerPool(new Consumer(),new Consumer(),new Consumer(),new Consumer());
		disruptor.start();//启动并初始化
		RingBuffer<PCDate> ringBuffer = disruptor.getRingBuffer();
		Producer producer = new Producer(ringBuffer);
		ByteBuffer bb = ByteBuffer.allocate(8);
		for(long l=0;true;l++){//生产者不断向缓冲区存入数据
			bb.putLong(0,l);
			producer.pushDate(bb);
			Thread.sleep(100);
			System.out.println("add data"+l);
		}
	}
}
选择合适的策略提高消费者的响应时间
Disruptor提供策略监控缓冲区的信息,由WaitStrategy接口封装
BlockingWaitStrategy是默认的策略,使用锁和条件监控数据和线程唤醒,涉及到线程切换,最节省CPU,高并发下性能最糟

SleepingWaitStrategy:循环中不断等待数据,先自旋,不成功,使用Thread.yield()让出CPU,最终使用LockSupport.partNamos(1)进行线程休眠,确保不占用太多CPU,延迟较高,异步日志

YieldingWaitStrategy:低延迟,消费者线程会不断循环监控缓冲区变化,循环内部,使用Thread.yield()让出CPU给别的线程执行时间最好有多于消费者线程数量的逻辑CPU数量

BusySpinWaitStrategy:死循环,疯狂监控缓冲区变化,会吃掉所有的CPU,物理CPU数量必须大于消费者线程数
CPU Cache的优化,解决伪共享的问题
Future模式

异步调用,无法立即给出数据,但会返回一个契机,可以凭借契约去重新获取数据



public interface Data {
	public String getResult();
}
public class FutureData implements Data{
	protected RealData realData = null;//FutureData是RealData的包装
	protected boolean isReady = false;
	public synchronized void setRealData(RealData realData){
		if(isReady){
			return;
		}
		this.realData = realData;
		isReady = true;
		notifyAll();//RealData已被注入,通知getResult
	}
	public synchronized String getResult(){
		while(!isReady){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return realData.result;
	}
}
public class RealData implements Data {
	protected final String result;
	public RealData(String result) {
		StringBuffer sb = new StringBuffer();
		for(int i=0;i<10;i++){
			sb.append(result);
			try {
				Thread.sleep(100);//休眠。表示处理时间长
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.result = sb.toString();
	}
	@Override
	public String getResult() {		
		return this.result;
	}
}
public class Client {
	public Data request(final String queryStr){
		final FutureData futureData = new FutureData();
		new Thread(){
			public void run(){
				//放入线程执行
				RealData realData = new RealData(queryStr);
				futureData.setRealData(realData);
			}
		}.start();
		return futureData;
	}
}
public class FutureMain {
	public static void main(String[] args) {
		Client client = new Client();
		Data data = client.request("name");//因为是futureData,立即返回
		System.out.println("请求完毕");
		try {
			Thread.sleep(2000);//这里作为其他业务处理
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("数据="+data.getResult());//返回数据
	}
}
JDK中的Future模式


Future接口类似于契约,run方法用于构建真实的数据,call方法返回需要构造的实际数据
public class RealData implements Callable<String> {
	private String result;
	public RealData(String result) {
		this.result = result;
	}
	@Override
	public String call() throws Exception {
		StringBuffer sb = new StringBuffer();
		for(int i=0;i<10;i++){
			sb.append(result);
			try {
				Thread.sleep(100);//休眠。表示处理时间长
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return sb.toString();
	}
}
public class FutureMain {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//构造FutureTask
		FutureTask<String> future = new FutureTask<>(new RealData("a"));
		ExecutorService eService = Executors.newFixedThreadPool(1);
		eService.submit(future);//执行FutureTask,相当于发送请求,开启线程进行RealData的call执行
		System.out.println("请求完毕");
		try {
			Thread.sleep(2000);//这里作为其他业务处理
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("数据="+future.get());//返回数据
	}
}
Future接口

boolean cancel(boolean mayInterruptIfRunning);//取消任务
boolean isCancelled();//是否已取消
boolean isDone();//是否已完成
V get() throws InterruptedException, ExecutionException;//取得返回对象
V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;//取得返回对象,可以设置超时时间
并行流水线

(B+C)*B/2无法并行,B+C没有完成,无法后续

可以将计算拆成三步

P1:A=B+C

P2:D=A*B

P3:D=D/2

public class Msg {
	public double i;
	public double j;
	public String orgStr = null;
	// P1算法
	public static class Plus implements Runnable {
		public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<>();
		@Override
		public void run() {
			while (true) {
				try {
					Msg msg = bq.take();
					msg.j = msg.i + msg.j;
					Msg.Multiply.bq.add(msg);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	// 乘法
	public static class Multiply implements Runnable {
		public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<>();
		@Override
		public void run() {
			while (true) {
				try {
					Msg msg = bq.take();
					msg.i = msg.i * msg.j;
					Div.bq.add(msg);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	// 除法
	public static class Div implements Runnable {
		public static BlockingQueue<Msg> bq = new LinkedBlockingQueue<>();
		@Override
		public void run() {
			while (true) {
				try {
					Msg msg = bq.take();
					msg.i = msg.i /2;
					System.out.println(msg.orgStr+"="+msg.i);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	public static void main(String[] args) {
		new Thread(new Plus()).start();
		new Thread(new Multiply()).start();
		new Thread(new Div()).start();
		for(int i=0;i<1000;i++){
			for(int j=0;j<1000;j++){
				Msg msg = new Msg();
				msg.i = i;
				msg.j = j;
				msg.orgStr ="("+i+"+"+j+")*"+i+"/2";
				Plus.bq.add(msg);
			}
		}
	}
}
并行搜索
可以将原始数据集合按照期望的线程数分割,让每个线程独立搜索
public class Array {

	static int[] arr;//定义一个数组,需要查找数组中元素

	public static class ThreadPool {
		static ExecutorService pool = Executors.newCachedThreadPool();//线程池
		static final int Thread_Num = 2;//线程数量
		static AtomicInteger result = new AtomicInteger(-1);//保存符合条件的元素在arr中的下标,-1表示没有找到
		//并发搜索要求每个线程查找数组中的一段位置,指定搜索的起始位置
		public static int search(int searchValue, int beginPos, int endPos) {
			int i = 0;
			for (i = beginPos; i < endPos; i++) {
				if (result.get() > 0) {//判断是否有其他线程找到,找到立即返回
					return result.get();
				}
				if (arr[i] == searchValue) {//表示找到了
					if (!result.compareAndSet(-1, i)) {//将结果保存result变量中,CAS操作,失败表示其他线程找到,返回结果
						return result.get();
					}
					return i;
				}
			}
			return -1;
		}
		//查找线程
		public static class SearchTask implements Callable<Integer>{
			int begin,end,searchValue;
			
			public SearchTask(int begin, int end, int searchValue) {
				this.begin = begin;
				this.end = end;
				this.searchValue = searchValue;
			}

			@Override
			public Integer call() throws Exception {
				int re = search(searchValue, begin, end);
				return re;
			}			
		}
		//并行查找,针对线程数量对arr数组划分,建立对应的任务交给线程池
		public static int pSearch(int searchValue) throws InterruptedException, ExecutionException{
			int subArrSize=arr.length/Thread_Num+1;
			List<Future<Integer>> re = new ArrayList<>();
			for(int i=0;i<arr.length;i++){//将线程划分成若干段
				int end = i+subArrSize;
				if(end>=arr.length)end = arr.length;
				re.add(pool.submit(new SearchTask(searchValue, i, end)));
			}
			//每个任务都会返回future对象,通过result共享彼此信息,当一个线程 成功返回后,其他线程都会返回
			for(Future<Integer> fu: re){
				if(fu.get()>=0){
					return fu.get();
				}
			}
			return -1;
		}
	}
}
并行排序
分离数据相关性:奇偶交换排序

冒泡排序:

public class Test {
	public static void main(String[] args)  {
		int [] arr = {1,10,5,8};
		for(int i=0;i<arr.length;i++){
			for(int j=0;j<arr.length-1;j++){
				if(arr[j]>arr[j+1]){
					int temp = arr[j];
					arr[j]=arr[j+1];
					arr[j+1] = temp;					
				}
			}
		}
		for(int k=0;k<arr.length;k++){
			System.out.println(arr[k]);
		}	
	}
}


每次交换的俩个元素存在数据冲突,每个元素,可能和前面或者后面元素交换,很难并行

奇偶交换排序排序过程分为奇交换和偶交换,奇交换总是比较奇数索引和相邻的后续元素,偶交换总是比较偶索引和其相邻的后续元素,奇交换和偶交换会成对出现,保证比较和交换涉及到每个元素


每一次交换和比较都可以独立进行

public class Test {
	public static void main(String[] args)  {
		int [] arr = {1,10,5,8};
		int exchFlag=1,start = 0;//exchFlag记录是否发生交换,start表示哪种交换
		while(exchFlag==1||start==1){//上一次比较是奇交换或者发生了数据交换,继续执行
			exchFlag=0;
			for(int i=start;i<arr.length-1;i+=2){
				if(arr[i]>arr[i+1]){
					int temp = arr[i+1];
					arr[i]=arr[i+1];
					arr[i+1]=temp;
					exchFlag=0;
				}
			}
			if(start==0){
				start=1;
			}else{
				start=0;
			}
		}
	}
}
并行模式

public class Test {
	static int [] arr = {1,10,5,8,3};
	static int exchFlag=1;
	static ExecutorService pool = Executors.newCachedThreadPool();//线程池
	static synchronized void setExchFlag(int v){
		 exchFlag=v;
	}
	static synchronized int getExchFlag(){
		 return exchFlag;
	}
	public static class OddEvenSortTask implements Runnable{
		int i;
		CountDownLatch latch;
		public OddEvenSortTask(int i, CountDownLatch latch) {//定义了奇偶排序任务类
			this.i = i;
			this.latch = latch;
		}
		@Override
		public void run() {
			if(arr[i]>arr[i+1]){
				int temp = arr[i];
				arr[i]=arr[i+1];
				arr[i+1]=temp;
				setExchFlag(1);
			}
			latch.countDown();
		}
		
	}
	public static void pOddEvenSort(int[]arr) throws InterruptedException{//并行排序的主体
		int start=0;
		while(getExchFlag()==1||start==1){
			setExchFlag(0);
			//偶数的数组长度,当start为1,只有len/2-1个线程
			CountDownLatch latch = new CountDownLatch(arr.length/2-(arr.length%2==0?start:0));//记录线程数量
			for(int i=start;i<arr.length-1;i+=2){
				pool.submit(new OddEvenSortTask(i, latch));
			}
			latch.await();//等待所有线程结束
			if(start==0){
				start=1;
			}else{
				start=0;
			}
		}
	}
	public static void main(String[] args) throws InterruptedException  {
		pOddEvenSort(arr);
		for(int i=0;i<arr.length;i++){
			System.out.println(arr[i]);
		}	
	}
}
改进的插入排序:希尔排序

一个未排序的数组(也可以是链表)可以分为俩部分,前半部分已排序,后半部分未排序,排序时,在未排序的部分中选择一个元素,将其插入有序数组中,直到排完序


public class Test {
	public static void main(String[] args)  {
		int [] arr = {1,10,5,8};
		int key,j;
		for(int i=1;i<arr.length;i++){
			//key为要准备插入的元素
			key=arr[i];
			j=i-1;
			while(j>=0&&arr[j]>key){//确认此元素的插入位置
				arr[j+1]=arr[j];
				j--;
			}
			arr[j+1]=key;//插入元素
		}
		for(int k=0;k<arr.length;k++){
			System.out.println(arr[k]);
		}	
	}
}
希尔排序将整个数组根据间隔h分割成若干个子数组,子数组相互穿插在一起,每一次排序,分别对每个子数组排序,每次排序,总是交换间隔为h的俩个元素

以下,所有的方块,圆,三角分别为一个子数组


每一组排序完成后,可以递减h的值,进行下轮更加精细的排序,直到h为1,此时等价于一次插入操作

串行

public class Test {
	public static void main(String[] args)  {
		int [] arr = {5,52,6,3,4,6,2,9,10,7};
		//计算出最大的h值
		int h=1;
		while(h<=arr.length/3){
			h=h*3+1;
		}
		while(h>0){
			for(int i=h;i<arr.length;i++){
				if(arr[i]<arr[i-h]){
					int tmp = arr[i];
					int j=i-h;
					while(j>=0&&arr[j]>tmp){
						arr[j+h]=arr[j];
						j-=h;					
					}
					arr[j+h]=tmp;
				}
			}
			//计算出下一个h值
			h=(h-1)/3;
		}
		for(int k=0;k<arr.length;k++){
			System.out.println(arr[k]);
		}	
	}
}

并行

public class Test {
	static ExecutorService pool = Executors.newCachedThreadPool();// 线程池
	static int[] arr = { 5, 52, 6, 3, 4, 6, 2, 9, 10, 7 };
	//并行任务,根据给定的起始位置和h进行排序
	public static class ShellSortTask implements Runnable {
		int i = 0;
		int h = 0;
		CountDownLatch l;
		public ShellSortTask(int i, int h, CountDownLatch l) {
			this.i = i;
			this.h = h;
			this.l = l;
		}
		@Override
		public void run() {
			if (arr[i] < arr[i - h]) {
				int tmp = arr[i];
				int j = i - h;
				while (j >= 0 && arr[j] > tmp) {
					arr[j + h] = arr[j];
					j -= h;
				}
				arr[j + h] = tmp;
			}
			l.countDown();
		}
	}
	public static void pShellSort(int[] arg) throws InterruptedException {
		// 计算出最大h值
		int h = 1;
		CountDownLatch latch = null;
		while (h < arr.length / 3) {
			h = h * 3 + 1;
		}
		while (h > 0) {
			System.out.println("h=" + h);
			if (h >= 4) {
				latch = new CountDownLatch(arr.length - h);
			}
			for (int i = h; i < arr.length; i++) {
				// 控制线程数量
				if(h>=4){
					pool.execute(new ShellSortTask(i, h, latch));
				}else{
					if (arr[i] < arr[i - h]) {
						int tmp = arr[i];
						int j = i - h;
						while (j >= 0 && arr[j] > tmp) {
							arr[j + h] = arr[j];
							j -= h;
						}
						arr[j + h] = tmp;
					}
				}			
			}
			//等待线程排序完成,进入下一次排序
			latch.await();
			// 计算出下一个h值
			h = (h - 1) / 3;
		}
	}
	public static void main(String[] args) throws InterruptedException {
		pShellSort(arr);
		for (int k = 0; k < arr.length; k++) {
			System.out.println(arr[k]);
		}
	}
}
矩阵乘法
第一个矩阵的列数和第二个矩阵的行数必须相同,如下图,矩阵A*矩阵B,新矩阵中每一个元素为矩阵A和B对应行列的乘积求和


基于Socket的服务端的多线程模式




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值