一、概念

1.进程与线程的区别

进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的 。
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)。

线程

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  • Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作 为线程的容器

二者对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

    2.并行与并发

  • 并发::在同一时刻,有多个指令在单个 CPU 上交替执行

image.png

  • 并行:在同一时刻,有多个指令在多个 CPU 上同时执行

image.png

3.同步和异步

同步异步:

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

二、使用线程

未开启线程并发

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. BranchThread t=new BranchThread();
  4. t.run();
  5. for(int i=0;i<=100;i++){
  6. System.out.println("主线程"+i);
  7. }
  8. }
  9. }
  10. class BranchThread extends Thread{
  11. public void run(){
  12. for(int i=0;i<=1000;i++){
  13. System.out.println("分支线程------------->"+i);
  14. }
  15. }
  16. }

1: 自上而下的顺序
2:先输出分支线程1-1000,输出完之后再输出
主线程1-100

JUC - 图4

1.继承Thread

实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法。

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. BranchThread t=new BranchThread();
  4. //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
  5. t.start();
  6. for(int i=0;i<=1000;i++){
  7. System.out.println("主线程"+i);
  8. }
  9. }
  10. }
  11. class BranchThread extends Thread{
  12. public void run(){
  13. for(int i=0;i<=1000;i++){
  14. System.out.println("分支线程------------->"+i);
  15. }
  16. }
  17. }

JUC - 图5

  1. "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
  2. 分支线程--------->0
  3. 主线程0
  4. 分支线程--------->1
  5. 主线程1
  6. 分支线程--------->2
  7. 分支线程--------->3
  8. 主线程2
  9. 分支线程--------->4
  10. 主线程3
  11. 分支线程--------->5
  12. 主线程4
  13. 分支线程--------->6
  14. 主线程5
  15. 分支线程--------->7
  16. 主线程6
  17. 分支线程--------->8
  18. 主线程7
  19. 分支线程--------->9
  20. 主线程8
  21. 分支线程--------->10
  22. 主线程9
  23. 分支线程--------->11
  24. 主线程10
  25. 分支线程--------->12
  26. 主线程11
  27. 主线程12
  28. 分支线程--------->13
  29. 主线程13
  30. 分支线程--------->14
  31. 主线程14
  32. 分支线程--------->15

2.实现Runnable接口

实现线程的第二种方式,编写一个类实现java.lang.Runnable接口。

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. /**
  4. * 创建一个可运行的对象
  5. * BranchThread r= new BranchThread();
  6. * 将可运行的对象封装成一个线程对象
  7. * Thread t=new Thread(r)
  8. */
  9. Thread t = new Thread(new BranchThread());//合并代码
  10. t.start();
  11. for(int i=0;i<=100;i++){
  12. System.out.println("主线程"+i);
  13. }
  14. }
  15. }
  16. class BranchThread implements Runnable{
  17. public void run(){
  18. for(int i=0;i<=100;i++){
  19. System.out.println("分支线程------------->"+i);
  20. }
  21. }
  22. }

采用匿名内部类的方式【第二种变形】

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. //创建线程对象,采用匿名内部类方式。
  4. Thread t = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. for (int i = 0; i <= 100; i++) {
  8. System.out.println("t线程----->" + i);
  9. }
  10. }
  11. });
  12. t.start();
  13. for (int i = 0; i <= 100; i++) {
  14. System.out.println("main线程----->" + i);
  15. }
  16. }
  17. }

3.实现Callable接口

实现线程的第三种方式:
实现Callable接口
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

  1. import java.util.concurrent.Callable;
  2. import java.util.concurrent.FutureTask; // JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。
  3. public class MyThread {
  4. public static void main(String[] args) throws Exception {
  5. // 第一步:创建一个“未来任务类”对象。
  6. // 参数非常重要,需要给一个Callable接口实现类对象。
  7. FutureTask task = new FutureTask(new Callable() {
  8. @Override
  9. public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
  10. // 线程执行一个任务,执行之后可能会有一个执行结果
  11. // 模拟执行
  12. System.out.println("call method begin");
  13. Thread.sleep(1000 * 10);
  14. System.out.println("call method end!");
  15. int a = 100;
  16. int b = 200;
  17. return a + b; //自动装箱(300结果变成Integer)
  18. }
  19. });
  20. // 创建线程对象
  21. Thread t = new Thread(task);
  22. // 启动线程
  23. t.start();
  24. // 这里是main方法,这是在主线程中。
  25. // 在主线程中,怎么获取t线程的返回结果?
  26. // get()方法的执行会导致“当前线程阻塞”
  27. Object obj = task.get();
  28. System.out.println("线程执行结果:" + obj);
  29. // main方法这里的程序要想执行必须等待get()方法的结束
  30. // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
  31. // 另一个线程执行是需要时间的。
  32. System.out.println("hello world!");
  33. }
  34. }
  1. call method begin
  2. /*等待sleep */
  3. call method end!
  4. /*等待另一个线程结束拿到300*/
  5. 线程执行结果:300
  6. hello world!