前言
多道程序设计技术是操作系统最早引入的技术,它的设计思想是允多个程序同时进入内存并运行,其目的是为了CPU的利用率,进而提高系统效率。
多道程序设计技术允许多个程序同时进入内存并运行,在这样的并发环境下,进程和线程的概念应运而生。
**这次我们一起来看一下什么是进程,什么是线程?**🧐
以下是本篇文章正文内容,该系列会持续更新,敬请关注。
什么是进程
进程是具有独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的独立单位。
什么是线程
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位。
线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
线程和进程的区别
-
调度: 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
-
并发性: 不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
-
拥有资源: 进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问所隶属进程的资源。
-
系统开销: 在创建或撤销进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤销线程时的开销。
线程和进程的关系
-
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
-
资源分配给进程,同一进程的所有线程共享该进程的所有资源。
-
线程是指进程内的一个执行单元,也是进程内的可调度实体。
-
线程在执行过程中需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
进程间的通信方式
-
管道(pipe)及有名管道(named pipe): 管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
-
信号(signal): 信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
-
消息队列(message queue): 消息队列是消息的链接表,它克服了以上两种通信方式中信号量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新信息,对消息队列有读权限的进程则可以从消息队列中读取消息。
-
共享内存(shared memory): 可以说是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
-
信号量(semaphore): 主要作为进程之间及同一种进程的不同进程之间的同步和互斥手段。
-
套接字(socket): 这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
到这里,我们来小结一下。🤔
线程是进程内的一个执行单元,是进程内的可调度实体。而进程是拥有系统资源的基本单位。
线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间。
每个线程有自己的执行堆栈和程序计数器为其执行上下文。而多线程主要是为了节约CPU时间。
那么如何创建线程呢?
🤔
创建线程的三种方式
1.继承Tread类
- 定义Tread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
- 创建Tread子类的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动该线程。
import org.junit.Test;
//实现多线程的第一种方式
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(name + " is running......");
}
}
public static void main(String[] args) {
Thread a = new MyThread("线程A");
Thread b = new MyThread("线程B");
Thread c = new MyThread("线程C");
a.start();
b.start();
c.start();
}
}
但在本质上,Tread也实现了Runnable接口
class Thread implements Runnable {
...
}
2.实现Runnable接口
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的执行体。
- 创建Runnable实现类的实例,并依此实例作为Tread的target来创建Tread对象,该Tread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动该线程。
import org.junit.Test;
//实现多线程的第二种方式
class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + " is running......");
}
}
public static void main(String[] args) {
Thread one = new Thread(new MyRunnable("线程1"));
Thread two = new Thread(new MyRunnable("线程2"));
Thread three = new Thread(new MyRunnable("线程3"));
one.start();
two.start();
three.start();
}
}
3.实现Callable接口
这个方法实际上是通过Callable和Future创建线程
。
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
注意:FutureTask是一个包装器,它通过接受Callable来创建,同时实现了Future和Runnable接口。
- 使用FutureTask对象作为Tread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
import org.junit.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//实现多线程的第三种方式
class MyCallable implements Callable<Integer> {
private String name;
private Integer number;
public MyCallable(String name, Integer number) {
this.name = name;
this.number = number;
}
@Override
public Integer call() throws Exception {
for (int i = 0; i < number; i++) {
System.out.println(name + " is running......");
}
return number;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//将Callable传入FutureTask
FutureTask<Integer> ft1 = new FutureTask<Integer>(new MyCallable("线程一", 3));
FutureTask<Integer> ft2 = new FutureTask<Integer>(new MyCallable("线程二", 4));
FutureTask<Integer> ft3 = new FutureTask<Integer>(new MyCallable("线程三", 5));
//将FutureTask传入Thread
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
Thread t3 = new Thread(ft3);
t1.start();
t2.start();
t3.start();
//获取线程返回结果
Integer number1 = ft1.get();
Integer number2 = ft2.get();
Integer number3 = ft3.get();
System.out.println("线程一返回结果:" + number1);
System.out.println("线程二返回结果:" + number2);
System.out.println("线程三返回结果:" + number3);
}
}
创建线程三种方式的比较
1.采用实现Runnable、Callable接口的方式创建多线程
优势:
(1)线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
(2)在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2.使用继承Thread类的方式创建多线程
优势:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势:
线程类已经继承了Thread类,所以不能再继承其他父类。
总体来说,实现Runnable接口这种方式更受欢迎,因为这不需要继承Tread类。
参考文章
同时也推荐看看这些文章
普歌-允异团队-【Java知识点】这些Java学习路上你必须知道的原理(2)为什么接口中没有构造方法而抽象类中有构造方法?
- 作者:昼伏夢想家
- 本文版权归作者和CSDN共有,欢迎转载,且在文章页面明显位置给出原文链接,未经作者同意必须保留此段声明,否则保留追究法律责任的权利。