一、概念
1.进程与线程的区别
进程
- 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的 。
- 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
- 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)。
线程
- 一个进程之内可以分为一到多个线程。
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
- Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作 为线程的容器
二者对比
- 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 进程间通信较为复杂
- 同一台计算机的进程通信称为 IPC(Inter-process communication)
- 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
- 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
-
2.并行与并发
并发::在同一时刻,有多个指令在单个 CPU 上交替执行
- 并行:在同一时刻,有多个指令在多个 CPU 上同时执行
3.同步和异步
同步异步:
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
二、使用线程
未开启线程并发
public class MyThread {
public static void main(String[] args) {
BranchThread t=new BranchThread();
t.run();
for(int i=0;i<=100;i++){
System.out.println("主线程"+i);
}
}
}
class BranchThread extends Thread{
public void run(){
for(int i=0;i<=1000;i++){
System.out.println("分支线程------------->"+i);
}
}
}
1: 自上而下的顺序
2:先输出分支线程1-1000,输出完之后再输出
主线程1-100
1.继承Thread
实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法。
public class MyThread {
public static void main(String[] args) {
BranchThread t=new BranchThread();
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
t.start();
for(int i=0;i<=1000;i++){
System.out.println("主线程"+i);
}
}
}
class BranchThread extends Thread{
public void run(){
for(int i=0;i<=1000;i++){
System.out.println("分支线程------------->"+i);
}
}
}
"C:\Program Files\Java\jdk-15.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar=64875:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\cao\IdeaProjects\untitled3\out\production\untitled Thread.ThreadTest02
分支线程--------->0
主线程0
分支线程--------->1
主线程1
分支线程--------->2
分支线程--------->3
主线程2
分支线程--------->4
主线程3
分支线程--------->5
主线程4
分支线程--------->6
主线程5
分支线程--------->7
主线程6
分支线程--------->8
主线程7
分支线程--------->9
主线程8
分支线程--------->10
主线程9
分支线程--------->11
主线程10
分支线程--------->12
主线程11
主线程12
分支线程--------->13
主线程13
分支线程--------->14
主线程14
分支线程--------->15
2.实现Runnable接口
实现线程的第二种方式,编写一个类实现java.lang.Runnable接口。
public class MyThread {
public static void main(String[] args) {
/**
* 创建一个可运行的对象
* BranchThread r= new BranchThread();
* 将可运行的对象封装成一个线程对象
* Thread t=new Thread(r)
*/
Thread t = new Thread(new BranchThread());//合并代码
t.start();
for(int i=0;i<=100;i++){
System.out.println("主线程"+i);
}
}
}
class BranchThread implements Runnable{
public void run(){
for(int i=0;i<=100;i++){
System.out.println("分支线程------------->"+i);
}
}
}
采用匿名内部类的方式【第二种变形】
public class MyThread {
public static void main(String[] args) {
//创建线程对象,采用匿名内部类方式。
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println("t线程----->" + i);
}
}
});
t.start();
for (int i = 0; i <= 100; i++) {
System.out.println("main线程----->" + i);
}
}
}
3.实现Callable接口
实现线程的第三种方式:
实现Callable接口
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; // JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。
public class MyThread {
public static void main(String[] args) throws Exception {
// 第一步:创建一个“未来任务类”对象。
// 参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //自动装箱(300结果变成Integer)
}
});
// 创建线程对象
Thread t = new Thread(task);
// 启动线程
t.start();
// 这里是main方法,这是在主线程中。
// 在主线程中,怎么获取t线程的返回结果?
// get()方法的执行会导致“当前线程阻塞”
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
// main方法这里的程序要想执行必须等待get()方法的结束
// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
// 另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}
call method begin
/*等待sleep */
call method end!
/*等待另一个线程结束拿到300*/
线程执行结果:300
hello world!