Java并发挑战

杀鸡用宰牛刀–利用多线程和自己写的缓存求递归斐波那契

虽然我这个操作然而并没有什么用
可能也当写着玩, 如果你们能学到知识那就更好了
多线程算法???


先来介绍一下使用到了哪些东西

注:如果没见过也没事, 我会慢慢讲解
一.一个计算结果的通用接口
二. 自己手写的具有良好伸缩性线程安全的缓存类
三.利用FutureTask实现的闭锁
四.多线程
五.信号量(许可证)Semaphore类


先看一下代码

实现说明, 我这只是拿来写着练手的, 并没有松耦合和良好的异常处理
而且绝对有更好的实现方式
首先: 通用接口
这里使用了泛型的键值对, A为键 V为值

import java.util.concurrent.ExecutionException;

public interface Computable<A,V> {
    public abstract V compute(A arg) throws InterruptedException, ExecutionException;
}

缓存类Memoizer
首先它具有良好的可伸缩性 和线程安全的特性
ConcurrentHashMap同样是基于散列的Map它内部通过同步机制更好的提供伸缩性和并发性
其内部使用了分段锁, 及时在串行程序下也具有不弱的性能
Future是一种闭锁的实现待会来详细讲

import java.math.BigInteger;
import java.util.Map;
import java.util.concurrent.*;


public class Memoizer<A, V> implements Computable<A ,V> {
    private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();
    private final Map<A, V> resultMap = new ConcurrentHashMap<>();
    private final Computable<A, V> computable;
    private static int N = 0;
    public Memoizer(Computable computable) {
        this.computable = computable;
    }

    public Map<A, V> getResultMap() {
        return resultMap;
    }

    @Override
    public V compute(A arg) throws InterruptedException {
        while(true) {
            Future<V> future = cache.get(arg);
            if(future == null) {
                Callable<V> callable = new Callable<V>() {
                    @Override
                    public V call() throws Exception {
                        return computable.compute(arg);
                    }
                };
                FutureTask<V> futureTask = new FutureTask<>(callable);
                future = cache.putIfAbsent(arg, futureTask);
                if(future == null) { future = futureTask; futureTask.run();}
            }
            try {
                V result = future.get();
                resultMap.put(arg, result);
                return result;
            } catch (CancellationException e){
                cache.remove(arg, future);
            } catch (ExecutionException e2) {
                throw new RuntimeException(e2);
            }
        }
    }
}

处理斐波那契函数列
这里我依赖了一个缓存的结果集, 虽然我很讨厌这样的写法
其实这样写改变了一些我写这个东西的初衷
因为访问这个类的时候如果没有在缓存中找到 他会自己计算不会去拿缓存中的值
我想要的是这个类去计算结果的时候先去找缓存 而不是 看一下缓存中没有就自己跑去递归计算了
可能动态代理帮我可以实现这样的功能 但是那是更复杂的一种实现, 有兴趣的可以下去试试

import java.math.BigInteger;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class FibFunction implements Computable<Integer, BigInteger> {

    private Map<Integer, BigInteger> map;

    public void setMap(final Map<Integer, BigInteger> map) {
        this.map = map;
    }

    @Override
    public BigInteger compute(Integer arg) throws InterruptedException, ExecutionException {
        if(map.containsKey(arg)) return map.get(arg);
        if (arg == 1 || arg == 2) return new BigInteger(1 + "");
        else return compute(arg - 1).add(compute(arg - 2));
    }
}

客户端类
这里可能看不懂的就是Semaphore 和 Callable了 没事接下来慢慢讲解

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.Scanner;
import java.util.concurrent.*;

public class Demo {
    public static void main(String[] args) throws InterruptedException, ExecutionException, IOException {
        Computable<Integer, BigInteger> computable = new FibFunction();
        Computable<Integer, BigInteger> cache = new Memoizer<>(computable);
        ((FibFunction) computable).setMap(((Memoizer<Integer, BigInteger>) cache).getResultMap());
        final Semaphore semaphore = new Semaphore(Runtime.getRuntime().availableProcessors());
        final BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(input.readLine());
        for(int i = 1; i <= n; i++) {
                final int k = i;
                final Thread thread = new Thread() {
                    @Override
                    public void run() {
                        try {
                            Callable<BigInteger> callable = new Callable() {
                                @Override
                                public BigInteger call() throws Exception {
                                    return cache.compute(k);
                                }
                            };
                            FutureTask futureTask = new FutureTask(callable);
                            futureTask.run();
                            futureTask.get();
                            semaphore.release();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                        }
                    }
                };
                semaphore.acquire();
                thread.start();
        }
        System.out.println(cache.compute(n));

    }
}

并发的效率问题

并发的安全性是一直以来困扰我们的问题, 因为我们需要不断在安全和性能之间去衡量
ConcurrentHashMap是线程安全的,有人可能会觉得麻烦 就直接使用
ConcurrentHashMap<Integer, BigInteger> 可能现实中的开发 每一步都是一个复杂的计算
比如一个方法f(27) 有两个线程都调用了f(27) 而这个方法是非常浪费时间的,
因为第一个线程调用f(27) 还没有得到结果也就没有存到map中 而后来的f(27) 正巧赶在第一个线程调用快结束了
也就块算出结果了 看了一下缓存中有没有键为27的 他发现没有
他又去计算了.
这样是很浪费时间的
那么我们希望 如果f(27) 正在计算 有一个状态可以标记f(27)已经在运行了
后进来的就只需要等待f(27)运行结束就可以了

FutureTask 和 Callable

FutureTask实现了Future 其表示的计算是通过Callable来实现的
相当于一种可生成结果的Runnable
其有三种状态 等待运行(Waiting to run) 正在运行 (Running) 和 运行完成(Completed)
当FutureTask进入运行完成状态后 它会永远的定制在这个状态上

FutureTask的主要方法get方法(返回结果的方法)
get方法的返回取决于任务的状态 若已经完成 则会立刻返回
否则会阻塞到任务完成然后返回结果或者抛出异常


线程边界(Semaphore)

Semaphore管理着一组虚拟的许可,就好比通行证
Semaphore的主要方法有两个(acquire) 和 (release)
构造方法, 其有一个int类型的有参构造方法 表示初始化的许可证
acquire获得许可证, 若没有许可证了 则一直阻塞到获得有线程返回了许可证 或者 被中断或者操作超时
release表示返回一个许可证


缓存的理念

如果你的程序不去为了效率(时间和空间)着想, 那么你的程序就是失败的

享元设计模式

它的理念很简单 其实就是一个共享工厂
无论是Web还是别的什么都有一些是一模一样的重用, 如果每用一次都去new 一个新的
这样无论是时间和空间都是吃不消的
举个例子java的String类 就是用了这个模式
享元模式的理念就是 在共享工厂中先去判断有没有 如果有 直接拿给你, 没有的再新建出来放入工厂
他非常适合那些有大部分东西都很相似的东西 只需要把相同的抽取出来, 不同的提取出来就可以

缓存也是一样 可能有些操作是非常耗费时间的, 而且这个操作可能会访问很多次, 而每次都会得到相同的结果
那么我们不妨把它缓存起来, 先去缓存中找, 如果缓存有就直接给它 否则就再去计算

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值