为什么即使服务器是单线程的,Java RMI在客户端上也有更多线程?

时间:2017-05-21 20:18:59

标签: java multithreading rmi

我最近在我的RMI程序中进行了大量测量,以便确定程序中最昂贵的操作(例如,对象的运行,运行方法等)。基本上,正如在下面的代码中可以看到的,我有一个float数组,可以作为参数传递给三个远程操作:power(所有元素),登录任何基(所有元素),并求和一个偏移量(到所有元素)。数组的大小为N = 10 ^ 8.

我的客户端是多线程的(K个线程)并将数组划分为N / K并将每个chunck传递给一个线程,而每个chunck都会调用一个RMI调用。服务器纯粹是单线程的。客户端和服务器在同一台计算机上运行。

对于客户端线程数K = 1,2,4,8,16,32,每种方法返回所需的时间如下(IN SECONDS - 10次迭代采样 - 机器:i7四核(8个逻辑处理器)):

  1. 任何基数的对数(2次调用Math.log):

    • K = 1 - > 7.306161
    • K = 2 - > 3.698500
    • K = 4 - > 2.788655
    • K = 8 - > 2.679441 (最好)
    • K = 16 - > 2.754160
    • K = 32 - > 2.812091
  2. Sum Offset(简单求和,不调用其他方法):

    • K = 1 - > 3.573020
    • K = 2 - > 1.864782 (最好)
    • K = 4 - > 1.874423
    • K = 8 - > 2.455411
    • K = 16 - > 2.752766
    • K = 32 - > 2.695977
  3. 我还在每种方法中测量了我的CPU使用情况:为了增加偏移量,大多数时候CPU使用率约为60%,而对数方法调用需要超过80%的CPU,多次达到100%。我也尝试过power方法(从下面的代码中抽象出来),它显示了与添加偏移量非常相似的结果(只是稍贵一点)。

    很容易得出结论,添加偏移量是非常便宜的,因此处理更多线程只会因为线程调度而变得更加昂贵。并且,由于计算对数更加昂贵,因此更多线程使问题更快,这就是为什么K = 8对于我的8台CPU机器来说是完美的。

    但是,服务器是单线程的!这怎么可能?在这种情况下,8个客户端线程如何比2个客户端线程做得更好?

    我在考虑这些结果时遇到了很多麻烦。任何帮助表示赞赏。每个模块的代码如下所示。

    Server.java的代码:

    package rmi;
    
    import java.rmi.Naming;
    import java.rmi.Remote;
    import java.rmi.registry.LocateRegistry;
    
    public class Server {
    
        Server(){
            try {
                System.setProperty("java.rmi.server.hostname", "localhost");
                LocateRegistry.createRegistry(1099);
                Service s = new ServiceImple();
                Naming.bind("Service", (Remote) s);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args){
            new Server();
        }
    }
    

    Client.java的代码:

    package rmi;
    
    import java.rmi.Naming;
    import java.util.ArrayList;
    
    public class Client {
    
        private static final int N = 100000000;
        private static final int K = 64;
        private static final int iterations = 1;
    
    
    
        public static void main(String[] args) throws InterruptedException {
    
            //Variable to hold current pseudo-random number:
            final int a = 25173;
            final int b = 13849;
            final int m = 3276;
    
            int x = m/2;
    
            //Create a list of lists:
            ArrayList<float[]> vector = new ArrayList<>();
    
            for (int i=0; i<K; i++){
                vector.add(new float[N/K]);
                for (int j=0; j<N/K; j++){
                    x = (a * x + b) % m;
                    vector.get(i)[j] = (float) x/m;
                }
            }
    
            long startTime = System.nanoTime();
    
            for (int count=0; count<iterations; count++){
    
                //Creates the list of threads:
                ArrayList<ClientThread> threads = new ArrayList<>();
    
                //Starts the threads
                for (int i=0; i<K; i++){
                    threads.add(new ClientThread(vector.get(i), N/K));
                    threads.get(i).start();
                }
    
                //Waits for threads to end:
                for (int i=0; i<K; i++){
                    threads.get(i).join();
                }
    
            }
    
            long estimatedTime = System.nanoTime() - startTime;
    
            estimatedTime /= iterations;
    
            System.out.println("Each loop took: "+(float)estimatedTime/1000000000);
        }
    
    }
    
    
    class ClientThread extends Thread{
    
        private float[] vector;
        private int vSize;
    
        public ClientThread(float[] vectorArg, int initSize){
            vector = vectorArg;
            vSize = initSize;
        }
    
        @Override
        public void run(){
            try {
                Service s = (Service) Naming.lookup("rmi://localhost:1099/Service");
    
                //Calculates log in RMI:
                //vector = (float[]) s.log(vector, vSize, 2);
    
                //Adds an offset in RMI:
                vector = (float[]) s.addOffset(vector, vSize, 100);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    Service.java代码:

    package rmi;
    
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    import java.util.List;
    
    public interface Service extends Remote {
    
        //Return log in parameter base of all elements in vector:
        public float[] log(float[] vector, int vSize, int base) throws RemoteException;
    
        //Adds an offset to all elements in vector:
        public float[] addOffset(float[] vector, int vSize, int offset) throws RemoteException;
    }
    

    ServiceImple.java的代码:

    package rmi;
    
    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    import java.util.List;
    
    
    public class ServiceImple extends UnicastRemoteObject implements Service{
    
        private static final long serialVersionUID = 1L;
    
        protected ServiceImple() throws RemoteException {
            super();
        }
    
    
        @Override
        public float[] log(float[] vector, int vSize, int base) throws RemoteException {
            for (int i=0; i<vSize; i=i+1){
                vector[i] = (float) (Math.log(vector[i])/Math.log(base));
            }
            return vector;
        }
    
        @Override
        public float[] addOffset(float[] vector, int vSize, int offset) throws RemoteException {
            for (int i=0; i<vSize; i=i+1){
                vector[i] = vector[i] + offset;
            }
            return vector;
        }
    }
    

2 个答案:

答案 0 :(得分:3)

RMI specification 3.2州:

  

由RMI运行时调度到远程对象实现的方法可能会也可能不会在单独的线程中执行。 RMI运行时不保证将远程对象调用映射到线程。

因此,至少不能保证在一个线程中执行RMI请求。 我们在实践中看到的是标准的Java RMI实现使用多个线程来处理请求。

答案 1 :(得分:1)

  

为什么即使服务器是单线程的,Java RMI在客户端上的多个线程也会更快?

因为你的假设是假的。 RMI服务器不是单线程的。