并行算法的应用-解决搬箱子这类谜题。

对于搬箱子,走迷宫这里问题,都包含一个初始位置,一个目标位置,一个目标集(起点与目标之间的有效移动),一个规则集(给定位置的合法移动,计算某位置的可能结果)这种问题称作一个谜题,首先看一下单线程中怎么解决这一来问题。

对这种问题进行抽象,其中:P 和M 代表位置和移动类。:

public interface Puzzle<P,M> {
	//初始位置
	P initialPosition();
	//是否为终点
	boolean isGoal(P position);
	//目前所在位置下一步可以移动的集合
	Set<M> legalMoves(P position);
	//一次移动过程
	P move(P position,M move);
}

 

每次移动的节点:

class Node<P, M> {
	final P pos;
	final M move;
	final Node<P, M> prev;
	public Node(P pos, M move, Node<P, M> prev) {
		super();
		this.pos = pos;
		this.move = move;
		this.prev = prev;
	}
	List<M> asMoveList(){
		List<M> solution=new LinkedList<>();
		for(Node<P, M> node=this;node.move!=null;node=node.prev) {
			solution.add(0,node.move);
		}
		return  solution;
	}
	
}

顺序化的解决问题:

public class SequentialPuzzleSolver<P,M> {
	private final Puzzle<P, M> puzzle;
	private final Set<P> seen=new HashSet<>();
	public SequentialPuzzleSolver(Puzzle<P, M> puzzle) {
		this.puzzle=puzzle;
	}
	public List<M> search(Node<P, M> node){
		if (!seen.contains(node.pos)) {
			seen.add(node.pos);
			if (puzzle.isGoal(node.pos)) {
				return node.asMoveList();
			}
			for(M move:puzzle.legalMoves(node.pos)) {
				P pos=puzzle.move(node.pos, move);
				Node<P, M> child=new Node<P, M>(pos, move, node);
				List<M> result=search(child);
				if (result!=null) {
					return result;
				}
			}
		}
		
		return null;
	}
}

SequentialPuzzleSolver是一个谜题框架的顺序话解决器,它在谜题空间中执行一个深度优先搜索,找到一个结果后终止,但并不一定是最优解。

因为每次计算和移动之间很少有资源竞争。因此有多个处理器可用时可以减少寻找结果所花费的时间。可以改写前一个解决过程,并加强一些并发性。串行顺序版本的程序执行就是深度优先搜索,搜索能力受到栈空间大小的限制,在java中会抛出(StackOverflowError)。

并发版本执行的是广度优先搜索,因此不会收到栈大小的影响,但仍然可能派出OOM异常(待搜索的结果集,已经访问过的记录超出内存可用空间)。

为了发现程序找到一个解法后可以停止搜索行为,需要一种方式来检查是否有哪个线程已经找出结果。如果我们需要获取程序第一个被计算出的结果,程序只有在没有发现任何方法时才去更新方案。这些条件描述了一种闭锁(latch)的行为,是一种可以携带结果的闭锁。

每个任务都会向闭锁请求方案,如果发现方案已经被发现,就停止。在找到一个方案之前,主线程需要等待。ValueLatch中的getValue会一直阻塞,直到有线程设置了value.

ValueLatch:

public class ValueLatch<T> {
	private T value=null;
	private final CountDownLatch done=new CountDownLatch(1);
	
	public boolean isSet() {
		return (done.getCount()==0);
	}
	public synchronized void setValue(T newValue) {
		if (!isSet()) {
			value=newValue;
			done.countDown();
		}
	}
	
	public T getValue() throws InterruptedException {
		done.await();
		synchronized (this) {
			return value;
		}
	}
	
}

 

并行的解决问题:

public class ConcurrentPuzzleSolver<P,M> {
	private final Puzzle<P, M> puzzle;
	private final ExecutorService exec;
	private final ConcurrentHashMap<P, Boolean> seen;
	final ValueLatch<Node<P, M>> solution
			=new ValueLatch<>();
	public ConcurrentPuzzleSolver(Puzzle<P, M> puzzle, ExecutorService exec, ConcurrentHashMap<P, Boolean> seen) {
		super();
		this.puzzle = puzzle;
		this.exec = exec;
		this.seen = seen;
	}
	
	public List<M> solve() throws InterruptedException{
		try {
			P p=puzzle.initialPosition();
			exec.execute(newTask(p, null, null));
			Node<P, M> solnNode=solution.getValue();
			return solnNode==null?null:solnNode.asMoveList();
		}finally {
			exec.shutdown();
		}
	}
	
	protected Runnable newTask(P pos,M move,Node<P, M> prev) {
		return new SolverTask(pos, move, prev);
	}
	class SolverTask extends Node<P, M> implements Runnable{
		public SolverTask(P pos, M move, Node<P, M> prev) {
			super(pos, move, prev);
		}

		@Override
		public void run() {
			if (solution.isSet()||seen.putIfAbsent(pos, true)!=null) {
				return ;
			}
			if (puzzle.isGoal(pos)) {
				solution.setValue(this);
			}else {
				for(M m:puzzle.legalMoves(pos)) {
					exec.execute(newTask(pos, m, prev));
				}
			}
		}
	}
}

但是以上代码存在一个问题,假如程序搜索完了所有的位置,但是还没有找到具体的方案。程序会一直阻塞。为了解决这个问题,可以在获取结果时设置超时时间,或者增加一个计数器,当所有节点搜索完成时将结果设置为null。

可以判断出任务不存在的解决问题:

public class PuzzleSolver<P,M> extends ConcurrentPuzzleSolver<P, M> {
	
	private final AtomicInteger taskCount=new AtomicInteger(0);
	
	public PuzzleSolver(Puzzle<P, M> puzzle, ExecutorService exec, ConcurrentHashMap<P, Boolean> seen) {
		super(puzzle, exec, seen);
	}
	
	class CountingSolverTask extends ConcurrentPuzzleSolver<P,M>.SolverTask{

		public CountingSolverTask(ConcurrentPuzzleSolver<P, M> concurrentPuzzleSolver, P pos, M move, Node<P,M> prev) {
			super(pos, move, prev);
			//创建一个任务时计数器加1
			taskCount.incrementAndGet();
		}
		public void run() {
			try {
				super.run();
			} finally {
				//当计数器再次回到0时,代表已经搜索完成
				if (taskCount.decrementAndGet()==0) {
					solution.setValue(null);
				}
			}
		}
	}
}

 

-------------------------------分割线-----------------------------------

参考资料:<<java并发变成实践>>

转载于:https://my.oschina.net/wang520/blog/2877783

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值