Java并发编程:如何创建线程?
在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务。下面先讲述一下Java中的应用程序和进程相关的概念知识,然后再阐述如何创建线程以及如何创建进程。下面是本文的目录大纲:
一.Java中关于应用程序和进程相关的概念
二.Java中如何创建线程
三.Java中如何创建进程
若有不正之处,请多多谅解并欢迎批评指正。
请尊重作者劳动成果,转载请标明原文链接:
http://www.cnblogs.com/dolphin0520/p/3913517.html
一.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
20
|
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
23
|
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
21
|
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/
《Java编程思想》
Java并发编程:如何创建线程?相关推荐
- Java 并发编程之创建线程,启动和常用方法
进程是处于运行过程中的程序:线程是进程中的一个执行单元:一个程序运行后至少有一个进程,一个进程中可以包含多个线程. Java 创建线程有三种方法: 1)继承 Thread 类 public class ...
- java并发编程实践(2)线程安全性
[0]README 0.0)本文部分文字描述转自:"java并发编程实战", 旨在学习"java并发编程实践(2)线程安全性" 的相关知识: 0.1)几个术语( ...
- Java 并发 多线程:创建线程的四种方式
Java 并发 多线程: 创建线程的四种方式 继承 Thread 类并重写 run 方法 实现 Runnable 接口 实现 Callable 接口 使用线程池的方式创建 1. 通过继承 Thread ...
- java并发编程第一课 线程的创建、停止和状态变更
开篇词: 由点及面,搭建你的 Java 并发知识网 你好,欢迎学习<Java 并发编程核心 78 讲>,我是讲师星星,一线互联网公司资深研发工程师,参与过集团内多个重点项目的设计与开发. ...
- Java并发编程—什么是线程?
原文作者:way_more 原文地址:Java 多线程常见基础面试题总结,面试必看! 目录 一.什么是线程和进程? 二.简要描述线程与进程的关系 三.FAQ 一.什么是线程和进程? 1.1. 何为进程 ...
- Java并发编程(01):线程的创建方式,状态周期管理
本文源码:GitHub·点这里 || GitEE·点这里 一.并发编程简介 1.基础概念 程序 与计算机系统操作有关的计算机程序.规程.规则,以及可能有的文件.文档及数据. 进程 进程是计算机中的程序 ...
- Java并发编程 synchronized保证线程安全的原理
文章转载致博客 blog.csdn.net/javazejian/- 自己稍加完善. 线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源 ...
- Java并发编程(02):线程核心机制,基础概念扩展
本文源码:GitHub·点这里 || GitEE·点这里 一.线程基本机制 1.概念描述 并发编程的特点是:可以将程序划分为多个分离且独立运行的任务,通过线程来驱动这些独立的任务执行,从而提升整体的效 ...
- Java并发编程(04):线程间通信,等待/通知机制
本文源码:GitHub·点这里 || GitEE·点这里 一.概念简介 1.线程通信 在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题, ...
- 【并发编程】创建线程的四种方式
上一篇我们初步认识了线程,现在我们来讲一下,创建线程的三种方式 1.继承Thread 类通过继承thread类,然后重写run方法(run方法中是线程真正执行的代码,runable也是如此)即可.当子 ...
最新文章
- 关于mysql中truncate
- NYOJ 155 求高精度幂
- 一些常用的CSS hack代码
- 命令行实现更强大的php交互
- 墨天轮2022年新春发布会暨年度数据库颁奖盛典即将开启!
- java中max函数blog_感受 lambda 之美!
- Element-UI 要怎么学?官方文档!
- [leetcode]208. 实现 Trie (前缀树)
- react根据中文获取拼音_学前家长建议收藏:你要的「趣味拼音课」来了
- 【车牌识别】基于matlab形态学车牌识别【含Matlab源码 1155期】
- 如何彻底卸载不需要的Mac屏保
- azw3转换为pdf_PDF怎么转换为PPT?PDF秒转PPT秘技!
- CareUEyes Pro(电脑防蓝光软件)官方中文版V2.0.0.9 | 电脑护眼软件下载
- 1380Problem C:zyf的A+B问题
- app推广渠道数据统计
- proftpd服务器搭建
- 盲盒是怎么赚钱的?(盲盒App的盈利逻辑)
- 云服务器1M带宽是上传吗,云主机1m带宽能干嘛?
- 解决已安装模块无法import的问题
- Spring源码:Advice接口
热门文章
- java webservice报文过长_年薪百万IT大牛分享及(京东,阿里,58)Java初中高级765道面试题...
- html代码大全贴音乐,网页音乐代码大全
- oracle 全局搜索字符串,oracle操作字符串:拼接、替换、截取、查找 _ 学编程-免费技术教程分享平台...
- phpmyadmin4.8.1远程文件包含漏洞
- 161011、oracle批量插入数据
- java 蓝桥杯算法训练 连续正整数的和(题解)
- 树莓派python开发教程_树莓派教程(基于python编程)--入门篇
- linux 内核同步--理解原子操作、自旋锁、信号量(可睡眠)、读写锁、RCU锁、PER_CPU变量、内存屏障
- 分布式选举协议:Bully
- (91)如何网表文件?