Java并发编程:如何创建线程?

在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务。下面先讲述一下Java中的应用程序和进程相关的概念知识,然后再阐述如何创建线程以及如何创建进程。下面是本文的目录大纲:

一.Java中关于应用程序和进程相关的概念

二.Java中如何创建线程

三.Java中如何创建进程

一.Java中关于应用程序和进程相关的概念

在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认为java.exe或者javaw.exe(windows下可以通过任务管理器查看)。Java采用的是单线程编程模型,即在我们自己的程序中如果没有主动创建线程的话,只会创建一个线程,通常称为主线程。但是要注意,虽然只有一个线程来执行任务,不代表JVM中只有一个线程,JVM实例在创建的时候,同时会创建很多其他的线程(比如垃圾收集器线程)。

由于Java采用的是单线程编程模型,因此在进行UI编程时要注意将耗时的操作放在子线程中进行,以避免阻塞主线程(在UI编程时,主线程即UI线程,用来处理用户的交互事件)。

二.Java中如何创建线程

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

  1.继承Thread类

继承Thread类的话,必须重写run方法,在run方法中定义需要执行的任务。


1

2

3

4

5

6

7

8

9

10

11

12

class MyThread extends Thread{

 private static int num = 0;

 public MyThread(){

 num++;

 }

 @Override

 public void run() {

 System.out.println("主动创建的第"+num+"个线程");

 }

}

创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public class Test {

 public static void main(String[] args) {

 MyThread thread = new MyThread();

 thread.start();

 }

}

class MyThread extends Thread{

 private static int num = 0;

 public MyThread(){

 num++;

 }

 @Override

 public void run() {

 System.out.println("主动创建的第"+num+"个线程");

 }

}

在上面代码中,通过调用start()方法,就会创建一个新的线程了。为了分清start()方法调用和run()方法调用的区别,请看下面一个例子:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public class Test {

 public static void main(String[] args) {

 System.out.println("主线程ID:"+Thread.currentThread().getId());

 MyThread thread1 = new MyThread("thread1");

 thread1.start();

 MyThread thread2 = new MyThread("thread2");

 thread2.run();

 }

}

class MyThread extends Thread{

 private String name;

 public MyThread(String name){

 this.name = name;

 }

 @Override

 public void run() {

 System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());

 }

}

运行结果:

从输出结果可以得出以下结论:

1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;

2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

2.实现Runnable接口

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

下面是一个例子:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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接口的。

在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

三.Java中如何创建进程

在Java中,可以通过两种方式来创建进程,总共涉及到5个主要的类。

第一种方式是通过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();

 }

}

要注意的是,exec方法不支持不定长参数(ProcessBuilder是支持不定长参数的),所以必须先把命令参数拼接好再传进去。

关于在Java中如何创建线程和进程的话,暂时就讲这么多了,感兴趣的朋友可以参考相关资料、

参考资料:

http://luckykapok918.blog.163.com/blog/static/205865043201210272168556/

http://www.cnblogs.com/ChrisWang/archive/2009/12/02/use-java-lang-process-and-processbuilder-to-create-native-application-process.html

http://lavasoft.blog.51cto.com/62575/15662/

问啊-一键呼叫程序员答题神器,牛人一对一服务,开发者编程必备官方网站:www.wenaaa.com

QQ群290551701 聚集很多互联网精英,技术总监,架构师,项目经理!开源技术研究,欢迎业内人士,大牛及新手有志于从事IT行业人员进入!

时间: 2024-10-21 15:17:21

Java并发编程:如何创建线程?的相关文章

【java并发编程实战】-----线程基本概念

学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习Java并发编程,共同进步,互相指导. 在学习Java并发之前我们需要先理解一些基本的概念:共享.可变.线程安全性.线程同步.原子性.可见性.有序性. 共享和可变 要编写线程安全的代码,其核心在于对共享的和可变的状态进行访问. "共享"就意味着变量可以被多个线程同时访问.我们知道系统中的资

Java并发编程系列(一)-线程的基本使用

最近在学习java并发编程基础.一切从简,以能理解概念为主. 并发编程肯定绕不过线程.这是最基础的. 那么就从在java中,如何使用线程开始. 继承Thread类 继承Thread类,重写run方法,new出对象,调用start方法. 在新启的线程里运行的就是重写的run方法. 1 /** 2 * 集成Thread类 实现run() 3 */ 4 public class C1 extends Thread { 5 6 @Override 7 public void run() { 8 try

Java并发编程(01):线程的创建方式,状态周期管理

本文源码:GitHub·点这里 || GitEE·点这里 一.并发编程简介 1.基础概念 程序 与计算机系统操作有关的计算机程序.规程.规则,以及可能有的文件.文档及数据. 进程 进程是计算机中的程序,关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体:在面向线程设计的计算机结构中,进程是线程的容器.程序是指令.数据及其组织形式的描述,进程是程序的实体. 线程 线程是操作系统能够进行运算调度的最小单

JAVA 并发编程之守护线程的创建与运行

java里有一种特殊的线程叫做守护线程(Daemon)线程.这种线程的优先级很低,通常来说,当同一个应用程序里没有其他的线程运行的时候,守护线程才运行.当程序中唯一运行的的线程是守护线程时,并且守护线程执行结束后 ,JVM也就结束了这个程序. 因为这种特性,守护线程通常被用来作为同一程序中普通线程(用户线程)的服务提供者.它们通常是无线循环的,以等待服务请求或者执行线程的任务.它们不能做重要工作,因为我们不可能知道守护线程什么时候获取CPU时钟,并且,在没有其他线程运行时,守护线程随时可以结束.

JAVA并发编程学习笔记------线程的三种创建方式

创建线程一般有如下几个方式: 1. 通过继承Thread类来创建一个线程: /** * 步骤1:定义一个继承Thread类的子类 * 步骤2:构造子类的一个对象 * 步骤3:启动线程: * */ public class ThreadTest{ public static void main(String[] args) { //构造子类对象 SubThread subThread = new SubThread(); //启动线程 subThread.start(); } } //定义继承Th

Java并发编程(02):线程核心机制,基础概念扩展

本文源码:GitHub·点这里 || GitEE·点这里 一.线程基本机制 1.概念描述 并发编程的特点是:可以将程序划分为多个分离且独立运行的任务,通过线程来驱动这些独立的任务执行,从而提升整体的效率.下面提供一个基础的演示案例. 2.应用案例 场景:假设有一个容器集合,需要拿出容器中的每个元素,进行加工处理,一般情况下直接遍历就好,如果数据偏大,可以根据线程数量对集合切割,每个线程处理一部分数据,这样处理时间就会减少很多. public class ExtendThread01 { publ

Java并发编程-如何终止线程

我们知道使用stop().suspend()等方法在终止与恢复线程有弊端,会造成线程不安全,那么问题来了,应该如何正确终止与恢复线程呢?这里可以使用两种方法: 1.使用interrupt()中断方法. 2.使用volatile boolean变量进行控制. 在使用interrupt方法之前,有必要介绍一下中断以及与interrupt相关的方法.中断可以理解为线程的一个标志位属性,表示一个运行中的线程是否被其他线程进行了中断操作.这里提到了其他线程,所以可以认为中断是线程之间进行通信的一种方式,简

JAVA - 并发编程 - 执行器和线程池

思考? 1 为什么要使用执行器和线程池? 2 执行器和线程是什么?怎么使用 执行器 线程执行器分离了任务的创建和执行,提高了线程的性能 线程池 避免了频繁地创建和销毁线程,达到线程对象的重用,可以根据项目灵活地控制并发的数量 ExecutorService (java.util.concurrent) 1 Executors.newCachedThreadPool() 可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程 2 Executors.newFixedT

java多线程编程(二创建线程)

1.概念           因为java是完全面向对象的,所以在java中,我们说的线程,就是Thread类的一个实例对象.所以,一个线程就是一个对象,它有自己字段和方法. 2.创建线程 创建线程有2种方法:1.扩展线程父类Thread类 , 2.实现Runnable接口.2种方法是由区别的. 提示:Thread类已经实现了Runnable接口,Thread类中不但实现了run方法,也定义了更多的,全面的线程对象的操作方法,而Runnable接口中只有run这一个方法. 通过扩展Thread类

Java并发编程学习:线程安全与锁优化

本文参考<深入理解java虚拟机第二版> 一.什么是线程安全? 这里我借<Java Concurrency In Practice>里面的话:当多个线程访问一个对象,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的. 我的理解:多线程访问一个对象,任何情况下,都能保持正确行为,就是对象就是安全的. 我们可以将Java语言中各种操作共享的数据分为以下5类:不可变.