接触java开发或者Android开发的时候,必不可少的会接触到进程、线程这样的概念和知识,那么进程和线程到底是什么,又有什么样的关联以及有什么特点呢?
概述
进程和线程,分别对应的英文单词是Process和Thread。
先说进程,我们一般使用的电脑或者手机在运行的时候,运行的每一个程序之间相互不影响,当一个程序异常崩溃之后,其他程序可以继续执行。简单来理解,每一个程序运行就是一个进程的开启。尤其是Android系统,正常来说会给每一个app分配单独的进程,赋予一定大小的内存资源,确保每一个app都是单独运营,彼此不影响。就相当于操作系统在你运行程序的时候,给你划分了一块地方,接下来你的程序就在这块地方自己蹦跶,不要出来扰乱别人的地方。所以进程之间是相互独立的,资源不共享的。(Android因为每一个程序分配的内存有限,超过内存上限就会OOM,所以有一些app功能较多,为了更好的运行,会申请另一个进程,这样一个app程序就可能对应多个进程,这个后续再说Android多进程相关的内容)
线程呢就是在这个进程中,处理一个一个任务的,在线程的地盘上,你可以买汽水,可以吃饭、可以看电影等等。这些都是一个一个的线程。线程算作轻量级的进程,是进程的一个执行单元,是CPU调度和分派的最小单元,一个进程是有一个或多个线程组成的,同一个进程的线程之间可以共享该进程的资源,同时一个线程可以创建、撤销另一个线程,比如你吃饭的时候可以去看电影或者取消播放电影。
特点和联系
通过以上描述,进程和线程的特点也大概提到了,在说明具体的特点前,我们需要有以下基本认识:
- 目前所有的操作系统都是支持多任务处理的
- 对于一个CPU来说,某一时间点只能处理一件事情
- 处理器执行速度较快,很多事情可以被快速轮换处理,所以在我们感知上只同时被处理的
进程的特点:
- 独立性
每一个进程都是独立的地盘、资源,相互之间不干涉。
- 动态性
进程是程序的执行过程,所以就会有被创建、被执行、暂停、撤销、销毁等过程和状态。
- 并发性
并发性也可以理解为异步,多个进程在系统的执行需要由系统进程管理统一调度以异步的方式通过一定的算法来保证各个进程能够协同共享使用CPU资源和其他资源。
线程的特点
- 最小性
线程是CPU调度和分配的最小单元。
- 动态性
线程也是经过创建、执行、挂起、销毁等过程和状态。
- 关联性
线程依赖于进程,每一个线程必然有一个父进程。
- 独立性
线程之间也是独立的,一个线程不知道父进程中是否有其他线程。
- 并发行
线程的执行是抢占式的,每一个线程在执行中都可能被挂起,腾出资源来执行另一个线程。
区别和关联
进程是资源分配的基本单位(划分地盘),线程是处理器调度的基本单位(调度CPU等)。
线程可以启动、撤销另一个线程,一个进程可以有多个线程抢占式式并发执行,每一个线程在执行中都可能被挂起,腾出处理器来执行另一个线程。
线程不能独立执行,线程依赖于进程,一个线程必然有一个父进程,一个程序至少有一个进程,一个进程至少有一个主线程,同一个父进程的线程共享该进程资源(地盘)。
线程之间是独立的,一个线程不知道父进程中是否有其他的线程存在。
调动使用CPU的是线程,在处理器运行的是线程。
同一进程的多个线程共享全局变量、静态变量,堆存储,每一个线程也有自己栈段,用来存储局部或临时变量。
进程的创建、销毁
创建
java中的进程类是Process。前面已经提到,每一个程序的运行基本上都会开启一个进程,然后执行进程的主线程。所以很多时候,我们并不直接去使用代码来创建一个进程,而是交由系统来为每一个程序自动创建进程。执行Java程序都伴随着Java虚拟机的执行,每一个程序都对应着一个虚拟机,可以理解为每一个虚拟机的启动就是一个进程的启动(当然其他语言就不是虚拟机,但是也有进程的概念),然后启动该进程的主线程,由主线程启动其他线程。对应的就是可执行的Java程序中必不可少的一个main方法,main方法是程序的起点,是程序的初始线程,其他线程都由它启动。(Android中启动机制和普通的java程序不一样,所以没有main方法,此处不多说)
通过代码启动一个进程的方法:在java.lang.Runtime类和Java.lang.ProcessBuilder可以来创建一个本地的进程。
具体的使用方法如下(Java1.8):
//打开指定路径的文件夹
Process p1 =Runtime.getRuntime().exec("open /Users/anonyper/Desktop/未命名文件夹");
Process p2 = new ProcessBuilder("open", "/Users/anonyper/Desktop/未命名文件夹").start();
- Runtime.getRuntime()的其他方法如下:
Process exec(String command)
Process exec(String [] cmdarray)
Process exec(String [] cmdarrag, String [] envp)
Process exec(String [] cmdarrag, String [] envp, File dir)
Process exec(String cmd, String [] envp)
Process exec(String command, String [] envp, File dir)
//可以传入不同的参数、环境变量、以及定义执行的目录
- Java.lang.ProcessBuilder的方法如下
public ProcessBuilder(String... var1) {...}
public ProcessBuilder(List<String> var1) {...}
public ProcessBuilder command(List<String> var1) {...}
public ProcessBuilder command(String... var1) {...}
//使用ProcessBuilder可以预先配置相关属性再创建进程,而且也可以在后续使用中随着需要改变相关属性,属性修改之后对已创建的进程没影响,只影响后续的start方法创建的进程。
销毁
只要java虚拟机中还有普通线程(用户线程)在执行,那么虚拟机就不会停止,当没有普通线程时,虚拟机中都是守护线程的话,则虚拟机(该进程)就会退出。
- 自行退出
我们创建一个基本的可执行的Java程序,该程序中用户线程执行完之后,该进程也就退出了。
//代码一
public class ProcessTest {
public static void main(String[] args) {
System.out.print("我是main 方法");
}
}
执行结果:
我是main 方法
Process finished with exit code 0 //可以看到,进程以code 0的结果结束了
//代码二
public class ProcessTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
int index = 0;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
System.out.println("我是用户线程2,用来计数的:" + index);
if (index == 5)
return;
}
}
}).start();
String name = null;
name.length();
System.out.println("我是main 方法");
}
}
执行结果:
Exception in thread "main" java.lang.NullPointerException
at com.test.java.ProcessTest.main(ProcessTest.java:29)
我是用户线程2,用来计数的:1
我是用户线程2,用来计数的:2
我是用户线程2,用来计数的:3
我是用户线程2,用来计数的:4
我是用户线程2,用来计数的:5
Process finished with exit code 1 // main线程遇到异常退出了,但是整个进程还未退出,直到线程2退出了才退出
- 手动结束
可以通过java.lang.System.exit(int code)方法结束(退出code为0代表正常退出,为其他代表异常):
//代码一
public class ProcessTest {
public static void main(String[] args) {
System.out.print("11111");
System.exit(0);
System.out.print("22222");
}
}
执行结果:
11111
Process finished with exit code 0 // 可以看到,没有打印最后的“22222”进程就退出了
//代码二
public class ProcessTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
int index = 0;
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
System.out.println("我是用户线程2,用来计数的:" + index);
if (index == 5)
return;
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
System.out.println("我是main 方法");
}
}
执行结果:
我是用户线程2,用来计数的:1
Process finished with exit code 0 // 线程2还未执行完,整个进程就退出了
线程的创建和销毁
创建和启动
java中的线程是Thread类,所以所有的线程对象都是Thread类或者其子类。java.lang.Thread类如下:
public class Thread implements Runnable {
...
private Runnable target;
...
public static native void sleep(long var0) throws InterruptedException;
...
private void init(ThreadGroup var1, Runnable var2, String var3, long var4) {
this.init(var1, var2, var3, var4, (AccessControlContext)null);
}
private void init(ThreadGroup var1, Runnable var2, String var3, long var4, AccessControlContext var6) {
...
this.target = var2;
...
}
}
...
public Thread() {
this.init((ThreadGroup)null, (Runnable)null, "Thread-" + nextThreadNum(), 0L);
}
public Thread(Runnable var1) {
this.init((ThreadGroup)null, var1, "Thread-" + nextThreadNum(), 0L);
}
Thread(Runnable var1, AccessControlContext var2) {
this.init((ThreadGroup)null, var1, "Thread-" + nextThreadNum(), 0L, var2);
}
public Thread(ThreadGroup var1, Runnable var2) {
this.init(var1, var2, "Thread-" + nextThreadNum(), 0L);
}
public Thread(String var1) {
this.init((ThreadGroup)null, (Runnable)null, var1, 0L);
}
public Thread(ThreadGroup var1, String var2) {
this.init(var1, (Runnable)null, var2, 0L);
}
public Thread(Runnable var1, String var2) {
this.init((ThreadGroup)null, var1, var2, 0L);
}
public Thread(ThreadGroup var1, Runnable var2, String var3) {
this.init(var1, var2, var3, 0L);
}
public Thread(ThreadGroup var1, Runnable var2, String var3, long var4) {
this.init(var1, var2, var3, var4);
}
...
public void run() {
if(this.target != null) {
this.target.run();
}
}
...
}
Runnable接口代码:
package java.lang;
@FunctionalInterface
public interface Runnable {
void run();
}
通过以上代码,可以看出来,想要创建一个线程有以下两种方法:
- 通过继承Thread类或其子类,在run方法中加入自己的逻辑代码,实例化该类对象来创建Thread对象
public class ThreadTest {
public static void main(String[] args) {
System.out.println("当前线程name: " + Thread.currentThread().getName());
new MyThread().start();
}
}
class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("当前线程name: " + getName());
}
}
//执行结果:
当前线程name: main
当前线程name: Thread-0
Process finished with exit code 0
- 实现Runnable接口,在run方法中加入自己的逻辑代码,将其传递给Thread构造函数,来创建Thread对象
public class ThreadTest {
public static void main(String[] args) {
System.out.println("当前线程name: " + Thread.currentThread().getName());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("当前线程name: " + Thread.currentThread().getName());
}
}).start();
}
}
//执行结果:
当前线程name: main
当前线程name: Thread-0
Process finished with exit code 0
- RunnableFuture接口是Runnable接口的子类,可以通过RunnableFuture的实现类FutureTask来作为Thread的target。
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
...
public FutureTask(Callable<V> var1) {
if(var1 == null) {
throw new NullPointerException();
} else {
this.callable = var1;
this.state = 0;
}
}
public FutureTask(Runnable var1, V var2) {
this.callable = Executors.callable(var1, var2);
this.state = 0;
}
...
}
public interface Callable<V> {
V call() throws Exception;
}
通过以上代码,我们可以实现一个Callable接口,实现起call方法作为FutureTask的执行体,然后再将该FutureTask对象作为Thread的target以此来实现线程:
public class ThreadTest {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
sleep(5000);
System.out.println("FutureTask 等待5秒后返回100");
return 100;
}
});
Thread thread = new Thread(futureTask, "futureTask");
thread.start();
try {
System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
System.out.println("futureTask.isCancelled():" + futureTask.isDone());
System.out.println(futureTask.get());
System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
System.out.println("futureTask.isCancelled():" + futureTask.isDone());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//执行结果:
futureTask.isCancelled():false
futureTask.isCancelled():false
FutureTask 等待5秒后返回100
100
futureTask.isCancelled():false
futureTask.isCancelled():true
Process finished with exit code 0 // 结果值在5秒后返回,后续的逻辑代码才执行
public class ThreadTest {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
sleep(5000);
System.out.println("FutureTask 等待5秒后返回100");
String name = null;
name.length();
return 100;
}
});
Thread thread = new Thread(futureTask, "futureTask");
thread.start();
try {
System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
System.out.println("futureTask.isCancelled():" + futureTask.isDone());
System.out.println(futureTask.get());
System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
System.out.println("futureTask.isCancelled():" + futureTask.isDone());
} catch (Exception e) {
e.printStackTrace();
}
}
}
//执行结果:
futureTask.isCancelled():false
futureTask.isCancelled():false
java.util.concurrent.ExecutionException: java.lang.NullPointerException
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.test.java.ThreadTest.main(ThreadTest.java:33)
Caused by: java.lang.NullPointerException
at com.test.java.ThreadTest$1.call(ThreadTest.java:24)
at com.test.java.ThreadTest$1.call(ThreadTest.java:18)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:745)
FutureTask 等待5秒后返回100
Process finished with exit code 0
//修改代码:
try {
System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
System.out.println("futureTask.isCancelled():" + futureTask.isDone());
//System.out.println(futureTask.get());
System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
System.out.println("futureTask.isCancelled():" + futureTask.isDone());
} catch (Exception e) {
e.printStackTrace();
}
//执行结果:
futureTask.isCancelled():false
futureTask.isCancelled():false
futureTask.isCancelled():false
futureTask.isCancelled():false
FutureTask 等待5秒后返回100
Process finished with exit code 0
Callable接口相比Runnable接口,call方法会有返回值,并且在程序错误时抛出异常。在使用futureTask.get()获取返回值时,当前线程会等待结果的返回后才会往下执行。如果call方法抛出异常,那么调用futureTask.get()的地方就需要捕获异常,要不当前线程就会中断。
总结
以上两种(callable和runnable算一种)方法区别是通过继承Thread类的方法创建线程时,多个线程之间无法共享线程类内的实例变量,但是通过Runnable接口创建的线程,因为runnable接口只是作为Thread对象的一个target,然后调用runnable接口的run方法作为线程执行体,多个线程可以共享一个target,这样多个线程之间就可以共享一个实例变量了。同时使用接口的方式,该线程类除了实现Runnable和Callable接口外,还可以继承其他类。
public class ThreadTest {
public static void main(String[] args) {
SellRunnable sellRunnable = new SellRunnable();
Thread thread1 = new Thread(sellRunnable, "1");
Thread thread2 = new Thread(sellRunnable, "2");
thread1.start();
thread2.start();
}
}
class SellRunnable implements Runnable {
//有十张票
int index = 10;
public synchronized void sell() {
index--;
System.out.println("售货窗口:" + Thread.currentThread().getName() + " 卖出了一张票,剩余:" + index);
}
@Override
public void run() {
while (index > 0) {
sell();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//执行结果:
售货窗口:1 卖出了一张票,剩余:9
售货窗口:2 卖出了一张票,剩余:8
售货窗口:2 卖出了一张票,剩余:7
售货窗口:1 卖出了一张票,剩余:6
售货窗口:1 卖出了一张票,剩余:5
售货窗口:2 卖出了一张票,剩余:4
售货窗口:1 卖出了一张票,剩余:3
售货窗口:2 卖出了一张票,剩余:2
售货窗口:1 卖出了一张票,剩余:1
售货窗口:2 卖出了一张票,剩余:0
Process finished with exit code 0
线程的结束
每一个线程都是一个顺序执行的代码段,线程的结束有以下几种方式:
- run所有代码逻辑执行完成,线程结束
- 线程在执行过程中抛出了一个异常或者error,线程结束
- 调用了该线程的stop、resume、suspend或者Runtime.runFinalizersOnExit这几个暴力方法,线程结束
使用stop等方法虽然可以强制结束线程,但是就如突然关掉电脑电源一样,这个操作是不安全的,它不会保证线程的资源被正确释放,所以这些方法已被废弃。
- 使用interrupt方法终端线程
使用interrupt方法不会真正的让线程停止,调用之后thread对象的isInterrupted()或Thread.interrupted()方法返回一个false,通过这个变量我们设置break或者return的方式使得线程退出。
使用interrupt方法会有两种情况,一种就是线程处于阻塞状态,比如sleep中,或者因为同步锁的原因等待其他线程释放资源,如果这是调用interrupt就会抛出InterruptException异常,捕获这个异常后可以退出线程。另一种情况就是没有阻塞的线程,需要获取isInterrupted()/Thread.interrupted()值判断是否需要退出线程,其实原理和第一种逻辑代码执行完退出一样。
public class ThreadTest {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
int index = 0;
while (index <= 3) {
System.out.println("线程1 测试代码执行完毕" + " index = " + index);
index++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "线程1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
int index = 0;
while (index <= 5) {
System.out.println("线程2 测试异常" + " index = " + index);
index++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (index == 2) {
String name = null;
name.length();
}
}
}
}, "线程2");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
int index = 0;
while (index <= 15) {
System.out.println("线程3 测试stop" + " index = " + index);
index++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
}, "线程3");
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
int index = 0;
while (!Thread.interrupted()) {
index++;
}
System.out.println("线程4 测试interrupt" + " index = " + index);
}
}, "线程4");
//启动线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
//标示是否打印过已结束判断
boolean flag1 = false;
boolean flag2 = false;
boolean flag3 = false;
boolean flag4 = false;
//标示是否调用stop方法
boolean stop_flag = false;
while (true) {
boolean thread_flag1 = thread1.isAlive();
boolean thread_flag2 = thread2.isAlive();
if (!thread_flag1 && !flag1) {
flag1 = true;
System.out.println("!!!!线程1结束了");
}
if (!thread_flag2 && !flag2) {
flag2 = true;
System.out.println("!!!!线程2结束了");
}
if (!thread_flag1 && !thread_flag2 && !stop_flag) {
stop_flag = true;
System.out.println("调用stop和interrupt方法");
thread3.stop();
thread4.interrupt();
}
boolean thread_flag3 = thread3.isAlive();
boolean thread_flag4 = thread4.isAlive();
if (!thread_flag3 && !flag3) {
flag3 = true;
System.out.println("!!!!线程3结束了");
}
if (!thread_flag4 && !flag4) {
flag4 = true;
System.out.println("!!!!线程4结束了");
}
if (!thread_flag1 && !thread_flag2 && !thread_flag3 && !thread_flag4) {
System.out.println("!!!!所有线程都结束了");
return;
}
}
}
}
//执行结果
线程1 测试代码执行完毕 index = 0
线程2 测试异常 index = 0
线程3 测试stop index = 0
线程1 测试代码执行完毕 index = 1
线程2 测试异常 index = 1
线程3 测试stop index = 1
线程3 测试stop index = 2
线程1 测试代码执行完毕 index = 2
!!!!线程2结束了
Exception in thread "线程2" java.lang.NullPointerException
at com.test.java.ThreadTest$2.run(ThreadTest.java:42)
at java.lang.Thread.run(Thread.java:745)
线程3 测试stop index = 3
线程1 测试代码执行完毕 index = 3
线程3 测试stop index = 4
!!!!线程1结束了
调用stop和interrupt方法
!!!!线程3结束了
线程4 测试interrupt index = 750987062
!!!!线程4结束了
!!!!所有线程都结束了
Process finished with exit code 0
以上,就是进程、线程的概念以及创建和结束方法。
进程与线程的创建和销毁
线程知识点总结
线程同步