2019独角兽企业重金招聘Python工程师标准>>>

无论是Android亦或者Java中或多或少需要调用底层的一些命令,执行一些参数;

此时我们需要用到Java的Process来创建一个子进程,之所以是子进程是因为此进程依赖于发起创建请求的进程,如果发起者被Kill那个子进程也将Kill。

对于Process相信使用过的朋友一定不会陌生,它具有如下特点:

1.创建简单

2.控制难

3.容易导致无法创建子进程

4.如果是多线程那么很有可能造成内存溢出

以上现象如果你只是偶尔使用一次,创建一个进程或许你什么都没有感觉到,但是如果你使用了多线程,进行了大量的创建,以上问题你都会遇到。

相关:http://blog.csdn.net/qiujuer/article/details/38142273,http://blog.csdn.net/qiujuer/article/details/38086071

这两个星期一直在研究上面的问题,我要做的软件是在Android中进行TraceRoute,由于手机不可能完全Root所以不能采用JNI来发送ICMP请求的方式,最终只能使用创建进程方式进行;具体实现思路是:使用PING命令来PING百度等地址,在PING命令中加入TTL,得到每一次的IP地址,当IP地址与目标IP地址符合时退出,并且还需要单独PING一次每一跳的延迟和丢包。

单线程:PING 百度 TTL=1 =》 得到IP,PING IP 得到延迟丢包,改变TTL,进行下一次PING,直到所得到的IP与目标(百度)一样时停止。按照上面的思路一次需要创建两个子进程,一般到百度时TTL大约为12跳左右,所以就是2*12=24个子进程;如果是在单线程下简单明了,但是速度慢,整个过程大约需要1分钟左右。

多线程:同时发起3个线程进行3跳测试TTL=(1,2,3),测试完成后测试下一批数据TTL=(4,5,6),如果也是12跳的话,那么也是24个子进程,但是整体耗时将会为1/3.可见此时效率较高。

但是多线程需要考虑的是线程的同步问题,以及得到数据后的写入问题,这些赞不谈,只谈进程问题。经过我的测试假如现在测试100个网站的TraceRoute数据,在上层控制一次测试4个网站,底层实现并发3个线程,此时在一定时间内将会同时存在3*4个进程。按照平均每个网站12跳来算:12*2*100=240个子进程,需要的子线程为12*100=120个。

这个时候问题来了,假如现在程序子进程不正常了,遇到了一个一定的问题导致进程无法执行完成,此时你的现象是:一个子进程卡住,随后创建的所有子进程都卡住。假如最上层线程做了任务时间限制,那么到时间后将会尝试销毁,但是你会发现无法销毁,所持有的线程也不会销毁。但是上层以为销毁掉了,然后继续进行下一批的数据测试,此时你的线程数量会逐渐增加,如果100任务下来你的线程或许会达到3*4*100=1200如果有前期没有这样的情况那个就是一半:600个线程左右,如果后期还有任务将会继续增加但是却永远不会销毁,但是我们知道JVM的内存是有限的,所以此时将会出现内存溢出。

以上就是我遇到的问题,我最先改为了等待线程完全返回后再进行下一批数据测试,此时内存溢出是解决了,但是任务却一直卡住在哪里了,永远也不走。我就在想要解决这一的问题需要解决根本上的问题才行,经过研究我发现在程序创建了子进程后JVM将会创建一个子进程管理线程:“ProcessManager”:

正常情况下该线程状态为Native,但是如果创建大量子进程后有可能会出现此线程为Monitor状态,过一段时间后所有创建子进程的线程状态也将会变为Monitor状态,然后将一直死锁,后面创建线程也是继续死锁,无法继续。

通过查看ProcessManager源码发现,其中启动了一个线程用于监听子进程状态,同时管理子进程,比如输出消息以及关闭子进程等操作,具体如下:

/*** Copyright (C) 2007 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package java.lang;import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
import java.util.logging.Logger;
import java.util.logging.Level;/**** Manages child processes.** <p>Harmony's native implementation (for comparison purposes):* http://tinyurl.com/3ytwuq*/
final class ProcessManager {/**** constant communicated from native code indicating that a* child died, but it was unable to determine the status*/private static final int WAIT_STATUS_UNKNOWN = -1;/**** constant communicated from native code indicating that there* are currently no children to wait for*/private static final int WAIT_STATUS_NO_CHILDREN = -2;/**** constant communicated from native code indicating that a wait()* call returned -1 and set an undocumented (and hence unexpected) errno*/private static final int WAIT_STATUS_STRANGE_ERRNO = -3;/**** Initializes native static state.*/static native void staticInitialize();static {staticInitialize();}/**** Map from pid to Process. We keep weak references to the Process objects* and clean up the entries when no more external references are left. The* process objects themselves don't require much memory, but file* descriptors (associated with stdin/out/err in this case) can be* a scarce resource.*/private final Map<Integer, ProcessReference> processReferences= new HashMap<Integer, ProcessReference>();/*** Keeps track of garbage-collected Processes. */private final ProcessReferenceQueue referenceQueue= new ProcessReferenceQueue();private ProcessManager() {// Spawn a thread to listen for signals from child processes.Thread processThread = new Thread(ProcessManager.class.getName()) {@Overridepublic void run() {watchChildren();}};processThread.setDaemon(true);processThread.start();}/**** Kills the process with the given ID.** @parm pid ID of process to kill*/private static native void kill(int pid) throws IOException;/**** Cleans up after garbage collected processes. Requires the lock on the* map.*/void cleanUp() {ProcessReference reference;while ((reference = referenceQueue.poll()) != null) {synchronized (processReferences) {processReferences.remove(reference.processId);}}}/**** Listens for signals from processes and calls back to* {@link #onExit(int,int)}.*/native void watchChildren();/**** Called by {@link #watchChildren()} when a child process exits.** @param pid ID of process that exited* @param exitValue value the process returned upon exit*/void onExit(int pid, int exitValue) {ProcessReference processReference = null;synchronized (processReferences) {cleanUp();if (pid >= 0) {processReference = processReferences.remove(pid);} else if (exitValue == WAIT_STATUS_NO_CHILDREN) {if (processReferences.isEmpty()) {/*** There are no eligible children; wait for one to be* added. The wait() will return due to the* notifyAll() call below.*/try {processReferences.wait();} catch (InterruptedException ex) {// This should never happen.throw new AssertionError("unexpected interrupt");}} else {/*** A new child was spawned just before we entered* the synchronized block. We can just fall through* without doing anything special and land back in* the native wait().*/}} else {// Something weird is happening; abort!throw new AssertionError("unexpected wait() behavior");}}if (processReference != null) {ProcessImpl process = processReference.get();if (process != null) {process.setExitValue(exitValue);}}}/**** Executes a native process. Fills in in, out, and err and returns the* new process ID upon success.*/static native int exec(String[] command, String[] environment,String workingDirectory, FileDescriptor in, FileDescriptor out,FileDescriptor err, boolean redirectErrorStream) throws IOException;/**** Executes a process and returns an object representing it.*/Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory,boolean redirectErrorStream) throws IOException {// Make sure we throw the same exceptions as the RI.if (taintedCommand == null) {throw new NullPointerException();}if (taintedCommand.length == 0) {throw new IndexOutOfBoundsException();}// Handle security and safety by copying mutable inputs and checking them.String[] command = taintedCommand.clone();String[] environment = taintedEnvironment != null ? taintedEnvironment.clone() : null;SecurityManager securityManager = System.getSecurityManager();if (securityManager != null) {securityManager.checkExec(command[0]);}// Check we're not passing null Strings to the native exec.for (String arg : command) {if (arg == null) {throw new NullPointerException();}}// The environment is allowed to be null or empty, but no element may be null.if (environment != null) {for (String env : environment) {if (env == null) {throw new NullPointerException();}}}FileDescriptor in = new FileDescriptor();FileDescriptor out = new FileDescriptor();FileDescriptor err = new FileDescriptor();String workingPath = (workingDirectory == null)? null: workingDirectory.getPath();// Ensure onExit() doesn't access the process map before we add our// entry.synchronized (processReferences) {int pid;try {pid = exec(command, environment, workingPath, in, out, err, redirectErrorStream);} catch (IOException e) {IOException wrapper = new IOException("Error running exec()." + " Command: " + Arrays.toString(command)+ " Working Directory: " + workingDirectory+ " Environment: " + Arrays.toString(environment));wrapper.initCause(e);throw wrapper;}ProcessImpl process = new ProcessImpl(pid, in, out, err);ProcessReference processReference= new ProcessReference(process, referenceQueue);processReferences.put(pid, processReference);/*** This will wake up the child monitor thread in case there* weren't previously any children to wait on.*/processReferences.notifyAll();return process;}}static class ProcessImpl extends Process {/*** Process ID. */final int id;final InputStream errorStream;/*** Reads output from process. */final InputStream inputStream;/*** Sends output to process. */final OutputStream outputStream;/*** The process's exit value. */Integer exitValue = null;final Object exitValueMutex = new Object();ProcessImpl(int id, FileDescriptor in, FileDescriptor out,FileDescriptor err) {this.id = id;this.errorStream = new ProcessInputStream(err);this.inputStream = new ProcessInputStream(in);this.outputStream = new ProcessOutputStream(out);}public void destroy() {try {kill(this.id);} catch (IOException e) {Logger.getLogger(Runtime.class.getName()).log(Level.FINE,"Failed to destroy process " + id + ".", e);}}public int exitValue() {synchronized (exitValueMutex) {if (exitValue == null) {throw new IllegalThreadStateException("Process has not yet terminated.");}return exitValue;}}public InputStream getErrorStream() {return this.errorStream;}public InputStream getInputStream() {return this.inputStream;}public OutputStream getOutputStream() {return this.outputStream;}public int waitFor() throws InterruptedException {synchronized (exitValueMutex) {while (exitValue == null) {exitValueMutex.wait();}return exitValue;}}void setExitValue(int exitValue) {synchronized (exitValueMutex) {this.exitValue = exitValue;exitValueMutex.notifyAll();}}@Overridepublic String toString() {return "Process[id=" + id + "]";  }}static class ProcessReference extends WeakReference<ProcessImpl> {final int processId;public ProcessReference(ProcessImpl referent,ProcessReferenceQueue referenceQueue) {super(referent, referenceQueue);this.processId = referent.id;}}static class ProcessReferenceQueue extends ReferenceQueue<ProcessImpl> {@Overridepublic ProcessReference poll() {// Why couldn't they get the generics right on ReferenceQueue? :(Object reference = super.poll();return (ProcessReference) reference;}}static final ProcessManager instance = new ProcessManager();/*** Gets the process manager. */static ProcessManager getInstance() {return instance;}/*** Automatically closes fd when collected. */private static class ProcessInputStream extends FileInputStream {private FileDescriptor fd;private ProcessInputStream(FileDescriptor fd) {super(fd);this.fd = fd;}@Overridepublic void close() throws IOException {try {super.close();} finally {synchronized (this) {if (fd != null && fd.valid()) {try {ProcessManager.close(fd);} finally {fd = null;}}}}}}/*** Automatically closes fd when collected. */private static class ProcessOutputStream extends FileOutputStream {private FileDescriptor fd;private ProcessOutputStream(FileDescriptor fd) {super(fd);this.fd = fd;}@Overridepublic void close() throws IOException {try {super.close();} finally {synchronized (this) {if (fd != null && fd.valid()) {try {ProcessManager.close(fd);} finally {fd = null;}}}}}}/*** Closes the given file descriptor. */private static native void close(FileDescriptor fd) throws IOException;
}

在其中有一个“ native void watchChildren();”方法,此方法为线程主方法,具体实现可以看看JNI,在其中回调了方法:“ void onExit(int pid, int exitValue);” 在方法中:

void onExit(int pid, int exitValue) {ProcessReference processReference = null;synchronized (processReferences) {cleanUp();if (pid >= 0) {processReference = processReferences.remove(pid);} else if (exitValue == WAIT_STATUS_NO_CHILDREN) {if (processReferences.isEmpty()) {/*** There are no eligible children; wait for one to be* added. The wait() will return due to the* notifyAll() call below.*/try {processReferences.wait();} catch (InterruptedException ex) {// This should never happen.throw new AssertionError("unexpected interrupt");}} else {/*** A new child was spawned just before we entered* the synchronized block. We can just fall through* without doing anything special and land back in* the native wait().*/}} else {// Something weird is happening; abort!throw new AssertionError("unexpected wait() behavior");}}if (processReference != null) {ProcessImpl process = processReference.get();if (process != null) {process.setExitValue(exitValue);}}}

此方法作用是删除子进程队列中子进程同时通知子进程 ProcessImpl已完成。

但是在方法:“watchChildren()”中如果出现System.in缓冲期满的情况那么进程将无法正常结束,它将一直等待缓冲区有空间存在,而缓冲区又是公共区间,如果一个出现等待那么后续子进程也将全部等待,如果缓冲区无法清空,那么所有子进程将会全部死锁掉。这就是导致子进程卡死的凶手。

知道问题关键点那么就会有人想办法解决,例如:

//...读取数据...process.waitFor();//....再次读取

这样的方式看似很好,但是你有没有想过有些数据无法及时返回,所以在 waitfor()之前读取很有可能没有数据导致进行 waitfor()等待,这时我们可以看看源码:

public int waitFor() throws InterruptedException {synchronized (exitValueMutex) {while (exitValue == null) {exitValueMutex.wait();}return exitValue;}}
        void setExitValue(int exitValue) {synchronized (exitValueMutex) {this.exitValue = exitValue;exitValueMutex.notifyAll();}}

这里可以看见假如没有退出值将会进行等待,直到通知发生,但是通知想要发生必须要靠“ ProcessManager ”线程来告诉你。但是假如在等待过程中出现了大量的数据,导致 System.IN 满了,此时“ ProcessManager ”线程很傻很傻的进入了等待状态中,也将无法进行通知,而这边也就无法往下走,无法到达第二次读取,所以第二次读取就很随机了,在大量数据下第二次读取基本上就是摆设,也就是说无法正常的执行,最终也将导致死锁。

解决办法也很简单,创建线程后我们可以创建一个线程来专门读取信息,直到“ProcessManager”线程通知结束的时候,才退出线程。

首先我们看看Process提供的“exitValue()”方法:

public int exitValue() {synchronized (exitValueMutex) {if (exitValue == null) {throw new IllegalThreadStateException("Process has not yet terminated.");}return exitValue;}}

可见在” exitValue “没有值时将会抛出异常而不会阻塞,所以可以得出:” exitValue() “与” waitfor() “都可以用于判断线程是否完成,但是一个是阻塞的一个是不阻塞的方法,在线程中当然使用不阻塞的来完成我们的工作:

/*** 实例化一个ProcessModel** @param process Process*/private ProcessModel(Process process) {//initthis.process = process;//getout = process.getOutputStream();in = process.getInputStream();err = process.getErrorStream();//inif (in != null) {isInReader = new InputStreamReader(in);bInReader = new BufferedReader(isInReader, BUFFER_LENGTH);}sbReader = new StringBuilder();//start read threadreadThread();}....................//读取结果private void read() {String str;//read Intry {while ((str = bInReader.readLine()) != null) {sbReader.append(str);sbReader.append(BREAK_LINE);}} catch (Exception e) {e.printStackTrace();Logs.e(TAG, e.getMessage());}}/*** 启动线程进行异步读取结果*/private void readThread() {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {//while (true) {try {process.exitValue();//read lastread();break;} catch (IllegalThreadStateException e) {read();}StaticFunction.sleepIgnoreInterrupt(300);}//read endint len;if (in != null) {try {while ((len = in.read(BUFFER)) > 0) {Logs.d(TAG, String.valueOf(len));}} catch (IOException e) {e.printStackTrace();Logs.e(TAG, e.getMessage());}}//closeclose();//doneisDone = true;}});thread.setName("DroidTestAgent.Test.TestModel.ProcessModel:ReadThread");thread.setDaemon(true);thread.start();}

当创建进程后把进程丢进我建立的类中实例化为一个进程管理类,随后启动线程,线程执行中调用进程的” exitValue()“ ,如果异常就进入读取数据,直到不异常时再次读取一次最后数据,随后退出循环,退出后还读取了一次底层的数据(这个其实可以不用要,纯属心理作用!)。最后写入完成标记。其中” StaticFunction.sleepIgnoreInterrupt(300); “是我写的静态方法用于休眠等待而已,也就是 Sleep ,只不过加入了 try catch 。

当然光是读取IN流是不行的,还有Error流,这个时候就需要两个线程来完成,一个也行。不过我为了简单采用了:ProcessBuilder类创建进程并重定向了错误流到IN流中,这样简化了操作。

而使用ProcessBuilder类需要注意的是同一个ProcessBuilder实例创建子进程的时候是需要进行线程同步操作的,因为如果并发操作将会导致进程参数错误等现象发生,所以建议加上线程互斥来实现,但是不建议重复创建ProcessBuilder实例,创建那么多实例,何不把所有子进程放在一个ProcessBuilder实例里边。减少内存消耗啊,手机伤不起啊。

有必要提出的是,当线程判断结束的时候,也就是退出值(exitvalue)有值得时候此时其实在”ProcessManager“线程中已经杀掉了进程了,此时在进程中其实没有此进程了,有的也就是执行后的数据流而已。所以正常结束情况下无需自己调用”destroy()“方法,调用后将会触发异常,说没有找到此进程。

public void destroy() {try {kill(this.id);} catch (IOException e) {Logger.getLogger(Runtime.class.getName()).log(Level.FINE,"Failed to destroy process " + id + ".", e);}}

终于讲完了,累啊;

最后给大家分享我自己弄得一个类(ProcessModel),大家喜欢就直接拿去,如果有好的建议希望大家提出来:

import com.droidtestagent.journal.Logs;
import com.droidtestagent.util.StaticFunction;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** Create By Qiujuer* 2014-08-05* <p/>* 执行命令行语句进程管理封装*/
public class ProcessModel {private static final String TAG = "ProcessModel";//换行符private static final String BREAK_LINE;//错误缓冲private static final byte[] BUFFER;//缓冲区大小private static final int BUFFER_LENGTH;//创建进程时需要互斥进行private static final Lock lock = new ReentrantLock();//ProcessBuilderprivate static final ProcessBuilder prc;final private Process process;final private InputStream in;final private InputStream err;final private OutputStream out;final private StringBuilder sbReader;private BufferedReader bInReader = null;private InputStreamReader isInReader = null;private boolean isDone;/*** 静态变量初始化*/static {BREAK_LINE = "\n";BUFFER_LENGTH = 128;BUFFER = new byte[BUFFER_LENGTH];prc = new ProcessBuilder();}/*** 实例化一个ProcessModel** @param process Process*/private ProcessModel(Process process) {//initthis.process = process;//getout = process.getOutputStream();in = process.getInputStream();err = process.getErrorStream();//inif (in != null) {isInReader = new InputStreamReader(in);bInReader = new BufferedReader(isInReader, BUFFER_LENGTH);}sbReader = new StringBuilder();//start read threadreadThread();}/*** 执行命令** @param params 命令参数 eg: "/system/bin/ping", "-c", "4", "-s", "100","www.qiujuer.net"*/public static ProcessModel create(String... params) {Process process = null;try {lock.lock();process = prc.command(params).redirectErrorStream(true).start();} catch (IOException e) {e.printStackTrace();} finally {//sleep 100StaticFunction.sleepIgnoreInterrupt(100);lock.unlock();}if (process == null)return null;return new ProcessModel(process);}/*** 通过Android底层实现进程关闭** @param process 进程*/public static void kill(Process process) {int pid = getProcessId(process);if (pid != 0) {try {android.os.Process.killProcess(pid);} catch (Exception e) {try {process.destroy();} catch (Exception ex) {//ex.printStackTrace();}}}}/*** 获取进程的ID** @param process 进程* @return id*/public static int getProcessId(Process process) {String str = process.toString();try {int i = str.indexOf("=") + 1;int j = str.indexOf("]");str = str.substring(i, j);return Integer.parseInt(str);} catch (Exception e) {return 0;}}//读取结果private void read() {String str;//read Intry {while ((str = bInReader.readLine()) != null) {sbReader.append(str);sbReader.append(BREAK_LINE);}} catch (Exception e) {e.printStackTrace();Logs.e(TAG, e.getMessage());}}/*** 启动线程进行异步读取结果*/private void readThread() {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {//while to endwhile (true) {try {process.exitValue();//read lastread();break;} catch (IllegalThreadStateException e) {read();}StaticFunction.sleepIgnoreInterrupt(300);}//read endint len;if (in != null) {try {while ((len = in.read(BUFFER)) > 0) {Logs.d(TAG, String.valueOf(len));}} catch (IOException e) {e.printStackTrace();Logs.e(TAG, e.getMessage());}}//closeclose();//doneisDone = true;}});thread.setName("DroidTestAgent.Test.TestModel.ProcessModel:ReadThread");thread.setDaemon(true);thread.start();}/*** 获取执行结果** @return 结果*/public String getResult() {//waite process setValuetry {process.waitFor();} catch (Exception e) {e.printStackTrace();Logs.e(TAG, e.getMessage());}//until startRead enwhile (true) {if (isDone)break;StaticFunction.sleepIgnoreInterrupt(100);}//returnif (sbReader.length() == 0)return null;elsereturn sbReader.toString();}/*** 关闭所有流*/private void close() {//close outif (out != null) {try {out.close();} catch (IOException e) {e.printStackTrace();}}//errif (err != null) {try {err.close();} catch (IOException e) {e.printStackTrace();}}//inif (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}}if (isInReader != null) {try {isInReader.close();} catch (IOException e) {e.printStackTrace();}}if (bInReader != null) {try {bInReader.close();} catch (IOException e) {e.printStackTrace();}}}/*** 销毁*/public void destroy() {//processtry {process.destroy();} catch (Exception ex) {kill(process);}}
}

想了想还是把代码托管到了GitHub上,方便以后分享其他的代码。
地址:Android Utils

非常欢迎大家找出不足发表问题。

转载于:https://my.oschina.net/qiujuer/blog/298073

[Android] [Java] Process 创建+控制+分析 经验浅谈相关推荐

  1. 上海2014科目二注意事项及经验浅谈(龙泉驾校)

    上海2014科目二注意事项及经验浅谈(龙泉驾校) 刚通过科目二考试,其间辛苦与压力,唯有同道之人可知.得益于网络分享,今也总结一番,希望对有需要的人有所帮助. 首先为大家提供倒桩与S弯的方法(这里讲的 ...

  2. SVN使用中的经验浅谈

    上一篇博客简单讲了在合作开发项目时使用SVN的准备工作,而这篇博客则重点在使用中的规范也好,注意事项也好或者使用规则也好.简单说一下使用他的小小经验! 在合作开发项目开始前,贾琳师哥向我们提出了使用S ...

  3. java方法区对象类型_浅谈Java内存区域与对象创建过程

    一.java内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则 ...

  4. java中单例的应用_浅谈Java中单例模式的几种应用

    目录 浅谈Java中单例模式的几种应用 第一种:懒汉式 第二种:饿汉式 第三种:双重检索式 第四种:注册登记式 第五种:内部类形式 浅谈Java中单例模式的几种应用 日常开发中,为了提高我们系统中对象 ...

  5. java的向下转型_浅谈Java向下转型的意义

    一开始学习 Java 时不重视向下转型.一直搞不清楚向下转型的意义和用途,不清楚其实就是不会,那开发的过程肯定也想不到用向下转型. 其实向上转型和向下转型都是很重要的,可能我们平时见向上转型多一点,向 ...

  6. WMS系统条码作业项目实施经验浅谈

    随着市场环境的变化,对现代的企业在物料仓储的仓库,都有了更高的管理要求,以顺应市场的变化,无论是传统的制造企业.贸易公司.电商公司.还是物流供应链公司,都产生了对仓库管理的变革需求,而不再是传统的进销 ...

  7. java执行jar中的main_浅谈java 执行jar包中的main方法

    浅谈java 执行jar包中的main方法 通过 OneJar 或 Maven 打包后 jar 文件,用命令: java -jar ****.jar 执行后总是运行指定的主方法,如果 jar 中有多个 ...

  8. mysql创建存储过程意义_浅谈一下mySql中创建存储过程

    首先说一下为什么要使用存储过程,存储过程是在数据库中预编译的程序代码,其执行效率显然要比从程序端传过去的sql语句要高的多,这样既可以节省网络带宽,又可以加快sql的执行速度,可以很好的提高系统的性能 ...

  9. java socket 异步回调函数_浅谈socket同步和异步、阻塞和非阻塞、I/O模型

    原标题:浅谈socket同步和异步.阻塞和非阻塞.I/O模型 在进行网络编程时,常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式 同步/异步主要针 ...

  10. Java hibernate假外键_浅谈hibernate急迫加载问题(多重外键关联)

    数据库结构如下 strategy中有外键member_id(关联member表)外键strategy_category(关联category表)而member表中有外键position_id(关联po ...

最新文章

  1. mysql中的钱null,mysql 中null总结
  2. golang变量定义细节及beego环境搭建细节记录
  3. B12_Numpy字符串函数(add,multiply,center,capitalize,title,lower,upper,split,join,replace,decode,splitline)
  4. Oracle中大批量删除数据的方法
  5. 微型计算机和pc的概念,微型计算机IBM-PC(0520)系统原理及应用
  6. RedHat系列软件管理(第二版) --源码包安装
  7. Mybatis(6)CURD增删改查操作
  8. Linux 下如何查找木马并处理
  9. mysql text类型效率_mysql使用笔记:vachar,char,text比较
  10. 架构设计之Spring-Session分布式集群会话管理
  11. 最近做的几道笔试题,很有意思
  12. MATLAB模糊控制算法,驾驶员制动意图识别
  13. 冰点文库下载器的使用
  14. 采访:蔡学镛谈复杂事务处理(CEP)
  15. mpa和pis_1psia等于多少mpa
  16. Wi-Fi:802.11ac new feature Beamforming
  17. 自制Beamer主题
  18. 【入门】Pytorch实现简单的图片分类器
  19. 安卓webview中键盘遮挡输入框如何解决
  20. if,while选择结构和while,dowhile,for循环的使用

热门文章

  1. android实现应用程序只有在第一次启动时显示引导界面
  2. python手机能学吗_学习大数据是否可以不学习Python?
  3. linux下用c语言写黄金矿工,c语言课程设计黄金矿工(提高篇)
  4. 8086cpu学习笔记(4):指令系统
  5. php sub pos,PHP pos()用法及代码示例
  6. 中位数±四分位数表达_Python数据分析:强大字符串处理工具,正则表达式
  7. C/C++ 文件读取操作 竞赛篇
  8. 蓝桥杯2018年第九届C/C++省赛B组第二题-明码
  9. Android的Wifi系统框架分析第一篇
  10. 设计模式 - (3)抽象工厂模式(创建型)