java并发编程-创建线程

一.Java中关于线程和进程相关的概念

   二.Java中如何创建线程

   三.Java中如何创建进程

四.面试种常问的三种线程创建方式的区别

若有不正之处,请多多谅解并欢迎批评指正。

一.Java中关于线程和进程相关的概念

一般来说,一个java应用程序对应着JVM线程, 即在我们自己的程序中如果没有主动创建线程的话,只会创建一个线程,通常称为主线程。当然这并不是说一个java程序只有一个线程,JVM实例在创建的时候,同时会创建很多其他的线程(比如垃圾收集器线程)。

我们习惯性称的java程序是一个进程,其实包含着很多线程,线程才是cpu执行的最小单位,这也就是说为什么一个程序可以有多个线程。

二.Java中如何创建线程

 在java中如果要创建线程的话,一般有三种方式:1)继承Thread类;2)实现Runnable接口;3)实现Callable接口

1.继承Thread线程类

class myThread extends Thread{
     private static integer num = 0;
     public MyThread(){
        num++;
    }
    @Override
    public void run(){
      System.out.println("主动创建的第"+num+"个线程");
    }
}

创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。

public  class  Test {
     public  static  void  main(String[] args)  {
         MyThread thread =  new  MyThread();
         thread.start();
     }
}

2.实现Runnable接口

  在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  class  Test {
     public  static  void  main(String[] args)  {
         System.out.println( "主线程ID:" +Thread.currentThread().getId());
         MyRunnable runnable =  new  MyRunnable();
         Thread thread =  new  Thread(runnable);
         thread.start();
     }
}
class  MyRunnable  implements  Runnable{
     
     public  MyRunnable() {
         }
     @Override
     public  void  run() {
         System.out.println( "子线程ID:" +Thread.currentThread().getId());
     }
}

   Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable对象作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这跟普通的方法调用没有任何区别。

  事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。所以实际上是在start()方法里面执行了run()方法.

3.实现Callable方法

Callable接口类似于Runable接口,但是Runable接口不会返回结果,并且无法抛出返回结果的异常,而Callable可以有返回值被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,如下例子:

public  class  Test {
     public  static  void  main(String[] args)  {
         System.out.println( "主线程ID:" +Thread.currentThread().getId());
//1.执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
        FutureTast<Integer> result = new FutureTask<Integer>(new myCallable(50));
new Thread(result).start();
//2.接收线程运算后的结果
    try{
Integer sum = result.get();
System.out.println(sum);
}catch(InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
class  MyCallable  implements  Callable{  
    private Integer num;
     public  MyRunnable(Integer number) {
 this.num = number;
}
     @Override
    public   Integer  call throws Exception () {
        Integer sum = 0;
for(int i=0;i<=num;i++)
sum+=i;
}
}

三.Java中如何创建进程

第一种方式是通过Runtime.exec()方法来创建一个进程,第二种方法是通过ProcessBuilder的start方法来创建进程。下面就来讲一讲这2种方式的区别和联系。

  首先要讲的是Process类,Process类是一个抽象类,在它里面主要有几个抽象的方法,这个可以通过查看Process类的源代码得知:

  位于java.lang.Process路径下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  abstract  class  Process
{
    
     abstract  public  OutputStream getOutputStream();    //获取进程的输出流
      
     abstract  public  InputStream getInputStream();     //获取进程的输入流
 
     abstract  public  InputStream getErrorStream();    //获取进程的错误流
 
     abstract  public  int  waitFor()  throws  InterruptedException;    //让进程等待
  
     abstract  public  int  exitValue();    //获取进程的退出标志
 
     abstract  public  void  destroy();    //摧毁进程
}

  1)通过ProcessBuilder创建进程

  ProcessBuilder是一个final类,它有两个构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  final  class  ProcessBuilder
{
     private  List<String> command;
     private  File directory;
     private  Map<String,String> environment;
     private  boolean  redirectErrorStream;
 
     public  ProcessBuilder(List<String> command) {
     if  (command ==  null )
         throw  new  NullPointerException();
     this .command = command;
     }
 
     public  ProcessBuilder(String... command) {
     this .command =  new  ArrayList<String>(command.length);
     for  (String arg : command)
         this .command.add(arg);
     }
....
}

   构造器中传递的是需要创建的进程的命令参数,第一个构造器是将命令参数放进List当中传进去,第二构造器是以不定长字符串的形式传进去。

  那么我们接着往下看,前面提到是通过ProcessBuilder的start方法来创建一个新进程的,我们看一下start方法中具体做了哪些事情。下面是start方法的具体实现源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public  Process start()  throws  IOException {
// Must convert to array first -- a malicious user-supplied
// list might try to circumvent the security check.
String[] cmdarray = command.toArray( new  String[command.size()]);
for  (String arg : cmdarray)
     if  (arg ==  null )
     throw  new  NullPointerException();
// Throws IndexOutOfBoundsException if command is empty
String prog = cmdarray[ 0 ];
 
SecurityManager security = System.getSecurityManager();
if  (security !=  null )
     security.checkExec(prog);
 
String dir = directory ==  null  null  : directory.toString();
 
try  {
     return  ProcessImpl.start(cmdarray,
                  environment,
                  dir,
                  redirectErrorStream);
catch  (IOException e) {
     // It's much easier for us to create a high-quality error
     // message than the low-level C code which found the problem.
     throw  new  IOException(
     "Cannot run program \""  + prog +  "\""
     + (dir ==  null  ""  " (in directory \""  + dir +  "\")" )
     ": "  + e.getMessage(),
     e);
}
}

   该方法返回一个Process对象,该方法的前面部分相当于是根据命令参数以及设置的工作目录进行一些参数设定,最重要的是try语句块里面的一句:

1
2
3
4
return  ProcessImpl.start(cmdarray,
                     environment,
                     dir,
                     redirectErrorStream);

   说明真正创建进程的是这一句,注意调用的是ProcessImpl类的start方法,此处可以知道start必然是一个静态方法。那么ProcessImpl又是什么类呢?该类同样位于java.lang.ProcessImpl路径下,看一下该类的具体实现:

  ProcessImpl也是一个final类,它继承了Process类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final  class  ProcessImpl  extends  Process {
 
     // System-dependent portion of ProcessBuilder.start()
     static  Process start(String cmdarray[],
              java.util.Map<String,String> environment,
              String dir,
              boolean  redirectErrorStream)
     throws  IOException
     {
     String envblock = ProcessEnvironment.toEnvironmentBlock(environment);
     return  new  ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);
     }
  ....
}

   这是ProcessImpl类的start方法的具体实现,而事实上start方法中是通过这句来创建一个ProcessImpl对象的:

1
return  new  ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);

   而在ProcessImpl中对Process类中的几个抽象方法进行了具体实现。

  说明事实上通过ProcessBuilder的start方法创建的是一个ProcessImpl对象。

  下面看一下具体使用ProcessBuilder创建进程的例子,比如我要通过ProcessBuilder来启动一个进程打开cmd,并获取ip地址信息,那么可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Test {
     public  static  void  main(String[] args)  throws  IOException  {
         ProcessBuilder pb =  new  ProcessBuilder( "cmd" , "/c" , "ipconfig/all" );
         Process process = pb.start();
         Scanner scanner =  new  Scanner(process.getInputStream());
         
         while (scanner.hasNextLine()){
             System.out.println(scanner.nextLine());
         }
         scanner.close();
     }
}

   第一步是最关键的,就是将命令字符串传给ProcessBuilder的构造器,一般来说,是把字符串中的每个独立的命令作为一个单独的参数,不过也可以按照顺序放入List中传进去。

  至于其他很多具体的用法不在此进行赘述,比如通过ProcessBuilder的environment方法和directory(File directory)设置进程的环境变量以及工作目录等,感兴趣的朋友可以查看相关API文档。

  2)通过Runtime的exec方法来创建进程

  首先还是来看一下Runtime类和exec方法的具体实现,Runtime,顾名思义,即运行时,表示当前进程所在的虚拟机实例。

  由于任何进程只会运行于一个虚拟机实例当中,所以在Runtime中采用了单例模式,即只会产生一个虚拟机实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public  class  Runtime {
     private  static  Runtime currentRuntime =  new  Runtime();
 
     /**
      * Returns the runtime object associated with the current Java application.
      * Most of the methods of class <code>Runtime</code> are instance
      * methods and must be invoked with respect to the current runtime object.
      *
      * @return  the <code>Runtime</code> object associated with the current
      *          Java application.
      */
     public  static  Runtime getRuntime() {
     return  currentRuntime;
     }
 
     /** Don't let anyone else instantiate this class */
     private  Runtime() {}
     ...
  }

   从这里可以看出,由于Runtime类的构造器是private的,所以只有通过getRuntime去获取Runtime的实例。接下来着重看一下exec方法 实现,在Runtime中有多个exec的不同重载实现,但真正最后执行的是这个版本的exec方法:

1
2
3
4
5
6
7
public  Process exec(String[] cmdarray, String[] envp, File dir)
    throws  IOException {
    return  new  ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
    }

   可以发现,事实上通过Runtime类的exec创建进程的话,最终还是通过ProcessBuilder类的start方法来创建的。

  下面看一个例子,看一下通过Runtime的exec如何创建进程,还是前面的例子,调用cmd,获取ip地址信息:

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Test {
     public  static  void  main(String[] args)  throws  IOException  {
         String cmd =  "cmd " + "/c " + "ipconfig/all" ;
         Process process = Runtime.getRuntime().exec(cmd);
         Scanner scanner =  new  Scanner(process.getInputStream());
         
         while (scanner.hasNextLine()){
             System.out.println(scanner.nextLine());
         }
         scanner.close();
     }

四.常见的面试题

线程的创建方式的区别:

(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值