目录
进程
进程:
是处于运行过程中的程序,具有一定的独立性,进程是系统进行资源分配和调度的一个独立单位。
进程包括三个特征:
- 独立性
进程是系统中独立的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间。(在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间) - 动态性
进程和程序的区别在于,程序是一个静态的指令集合;而进程是一个正在系统中活动的指令集合。在进程中加入了时间概念。进程具有自己的生命周期和各种不同的状态。 - 并发性
多个进程可以在单个处理器上并发执行,多个进程之间不会相互影响。
并行和并发
并行:在同一时刻,由多个指令在多个处理器上同时执行。
并发:在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果。
线程
线程:
是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源。
线程的执行是抢占式的。
在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。
线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。
注意 如果多次调用start() 方法,则会出现异常Exception inthread”main”java.lang.IllegalThreadStateException。
线程方法说明
注意:
- Thread.java类中的start() 方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run() 方法。这个过程其实就是让系统安排一个时间来调用Thread中的run() 方法,也就是使线程得到运行,启动线程,具有异步执行的效果。
- 如果调用代码thread.run()就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才可以执行后面的代码。
- 执行start()方法的顺序不代表线程启动的顺序。
线程的创建
java创建线程,主要有以下三种方式:
继承Thread类创建线程类
步骤:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
在默认情况下,主线程的名字为 main,用户启动的多个线程的名字依次为:Thread-0,Thread-1,…,Thread-n等
案例如下:
public class FirstThread extends Thread {
private int i;
@Override
public void run() {
for (; i < 100; i++) {
// 直接使用this就可以获取当前线程
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
// 创建并启动第一个线程
new FirstThread().start();
// 创建并启动第二个线程
new FirstThread().start();
}
}
}
}
结论:
上面的程序共三个线程,分别是main,Thread-0,Thread-1。Thread-0和Thread-1两个线程输出的i变量不连续。因为i是FirstThread的实例变量,程序每次创建线程对象时都需要创建一个FirstThread对象,所以不能共享该实例变量。
实现Runnable接口创建线程类
具体步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法是该线程的执行体
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
- 调用线程对象的start()方法来启动该线程
注意:
Runnable接口中只包含一个抽象方法,从Java8开始,Runable接口是用了@FunctionalInterface修饰,也就是说,Runnable接口是函数式接口,可使用Lambda表达式式创建Runnable对象。
Callable接口也是函数式接口。
案例如下:
public class SecondThread implements Runnable {
private int i = 0;
@Override
public void run() {
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 40) {
SecondThread thread = new SecondThread();
// 通过new Thread(target, name)创建新线程
new Thread(thread, "Thread 1").start();
new Thread(thread, "Thread 2").start();
}
}
}
}
结论:
两个子线程的i变量是连续的,也就是说Runnable接口的方式创建的多个线程可以共享线程类的实例变量。
因为在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target,所以多个线程可以共享一个线程类的实例变量。
使用Callable和Future创建线程
从Java5开始,Java提供了Callable接口,该接口像是Runnable接口的增强版,Callable接口提供了一个call方法可以作为线程执行体,但call()方法比run()方法功能更强大。
Callable接口是函数式接口,所以可以使用Lambda表达式创建Callable对象。
- call()方法可以有返回值
- call()方法可以申明抛出异常
创建并启动有返回值的线程步骤如下:
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该方法有返回值,再创建Callable实现类的实例。从Java8开始,可以直接使用Lambda表达式创建Callable对象
- 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
- 使用FutureTask对象作为Thread对象的target创建并启动新线程
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
案例如下,
第一种方式,不使用Lambda表达式:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThirdThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i值为: " + i);
}
return i;
}
public static void main(String[] args) {
// 创建Callable对象
ThirdThread thread = new ThirdThread();
// FutureTask类来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>(thread);
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量j值为: " + j);
if (j == 40) {
// 实质还是以Callable对象来创建并启动线程
new Thread(task, "有返回值的线程").start();
}
}
try {
// 获取线程返回值
System.out.println("子线程的返回值:" + task.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
第二种方式,使用Lambda表达式:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FourThread {
public static void main(String[] args) {
FourThread thread = new FourThread();
// 先使用Lambda表达式创建Callable<Integer>对象
// 使用FutureTask类来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>) () -> {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i值为: " + i);
}
return i;
});
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量j值为: " + j);
if (j == 40) {
// 实质还是以Callable对象来创建并启动线程
new Thread(task, "有返回值的线程").start();
}
}
try {
// 获取线程返回值
System.out.println("子线程的返回值:" + task.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
注意:
程序最后调用了FutureTask对象的get()方法来返回call()方法的返回值—-该方法将导致主线程被阻塞,直到call()方法结束并返回为止。
创建线程的三种方式的对比
实现Runnable接口和实现Callable接口的方式基本相同,只是Callable接口定义的方法有返回值,并且可以声明抛出异常而已。他们可以归为一种。
采用实现Runnable接口、Callable接口的方式创建多线程
优点:
- 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类
- 多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想
缺点:
- 编程稍微复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法
采用继承Thread类的方式创建多线程
优点:
- 编写简单,如果需要访问当前线程,无须使用Thread.currentThread()方法,直接使用this即可获得当前线程
缺点:
- 因为线程类已经继承了Thread类,不能再继承其他父类
守护线程
在Java线程中有两种线程,一种是用户线程,另一种是守护线程。
守护线程是一种特殊的线程,它的特性有“陪伴”的含义,当进程中不存在非守护线程了,则守护线程自动销毁。
典型的守护线程就是垃圾回收线程
,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。
用个比较通俗的比喻来解释一下“守护线程”:任何一个守护线程都是整个JVM中所有非守护线程的“保姆”,只要当前JVM实例中存在任何一个非守护线程没有结束,守护线程就在工作,只有当最后一个非守护线程结束时,守护线程才随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),它就是一个很称职的守护者。
案例如下:
public class DaemonThread {
public static void main(String[] args) {
try {
MyThread myThread = new MyThread();
myThread.setDaemon(true);
myThread.start();
Thread.sleep(5000);
System.out.println("当我结束,thread对象也不再打印");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
super.run();
try {
while (true) {
i++;
System.out.println("i=" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
i=1
i=2
i=3
i=4
i=5
当我结束,thread对象也不再打印
在代码中,我们使用了setDaemon(true),所以myThread对象的线程为Daemon线程,所以此时只有main线程是唯一一个非守护线程,一旦main线程运行结束,守护线程也退出了,然后整个程序结束工作。
如果我们没有设置守护线程,即把上面这行代码去掉,我们再运行起来,程序就会一直运行下去。因为myThread对象的线程也是非守护线程,它还在一直运行,所以整个程序不会停止。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/155835.html