> 文章列表 > Java多线程基础篇|JUC并发编程基础篇

Java多线程基础篇|JUC并发编程基础篇

Java多线程基础篇|JUC并发编程基础篇

一、进程线程基本概念

进程和线程不是一开始就有的,而是随着需要才出现的概念。

1、进程产生的背景

最初计算机只能接受一些特定的指令,用户输入一个指令,计算机就做一个操作。但是用户输入的速度是远远低于计算机运算的速度的,所以计算机有大量时间是在等待用户的输入,也就是 CPU 一直处于空闲状态,CPU 的利用率非常低。

批处理操作系统

后来有了批处理操作系统,把一些系列的指令写成一个清单,一次性把这个清单交给计算机,然后计算机会逐行读取指令,并把结果输出到另外一个磁盘中。

批处理操作系统的出现虽然提高了计算机的效率,但是由于批处理操作系统的指令运行方式仍然是串行的,内存中始终只有一个程序在运行。这种批处理操作系统并不理想,因为在同一时刻,CPU 只能执行一个程序的指令,也就是内存中只能运行一个程序。

进程的提出

由于内存中只能运行一个程序,于是计算机科学家们提出了进程的概念。

进程就是应用程序在内存中分配的空间,也就是在内存中可以同时存在多个程序在运行,并且各个应用程序(进程)之间互不干扰。每一个进程都保存程序运行的状态。

程序:指能完成某些功能的代码集合。

在计算机中的 CPU 采用时间片的方式运行每一个进程。CPU 为每一个进程会分配一个时间片,如果时间片结束时进程还在运行,则暂停这个进程的运行,并且 CPU 分配给另外一个进程(这个过程就是上下文切换)。如果进程在时间片结束前阻塞或结束,则 CPU 立即进行切换,不用等待时间片用完。

需要注意 CPU 进行上下文切换是非常耗时的操作,因为要在进程切换前会保存当前进程的状态(进程的标识,进程使用的资源等),方便在下次获取到 CPU 时间片之后根据之前保存的状态进行恢复,接着继续执行。

使用 CPU时间片 + 进程的方式,在宏观上觉得在同一时间段执行了多个任务,那是因为 CPU 的运算速度太快了,所以看上去像是在并发处理任务。其实在在单核 CPU 来说,任意时刻只有一个程序在执行。

并发:在同一时间段,处理多个任务。

并行:在同一时刻,处理多个任务。

对操作系统的要求进一步提高

虽然进程的出现,使得操作系统的性能大大提升,但是随着时间的推移,人们并不满足一个进程在一段时间只能做一件事情,如果一个进程有多个子任务时,只能逐个得执行这些子任务,很影响效率。

比如:我们电脑上的杀毒软件,我们在扫描病毒的同时不能进行垃圾扫描。我们必须要等待病毒扫描完成之后,才能进行垃圾扫描。

线程的提出

我们能不能让这些子任务同时执行呢?于是线程的概念就被提出来了。线程是比进程更小的单位,一个程序就是一个进程,而一个进程里面可以包含一个或多个线程。

Java多线程基础篇|JUC并发编程基础篇

比如:我们电脑上的杀毒软件,可以一边进行病毒扫描,一边进行垃圾扫描。

进程让操作系统的并发性成为了可能,而线程让进程的内部并发成为了可能。

既然进程也可以实现并发,那为啥还要提出进程呢?

  • 进程的通信比较复杂,数据不容易共享,不同进程之间有内存屏障。而线程方便数据的共享,也方便线程之间的通信。
  • 进程是重量级的,切换进程的开销比较大,不但需要保存寄存器和栈信息,还要进行资源的分配回收以及页调度。而线程只需要保存寄存器和栈信息,开销比较小。

二、多线程

上面我们讲了进程和线程的来由。那么在 Java 中如何创建线程呢?

1、继承Thread类

class MyThread extends Thread {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}
public class Demo04 {public static void main(String[] args) {MyThread thread1 = new MyThread();thread1.setName("子线程1");thread1.start();MyThread thread2 = new MyThread();thread2.setName("子线程2");thread2.start();System.out.println(Thread.currentThread().getName());}
}

注意:同一个线程不能多次调用start()方法,否则会报 IllegalThreadStateException异常。

start() 源码:

private volatile int threadStatus = 0;
public synchronized void start() {/* This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM. A zero status value corresponds to state "NEW".*/if (threadStatus != 0)throw new IllegalThreadStateException();/* Notify the group that this thread is about to be started* so that it can be added to the group's list of threads* and the group's unstarted count can be decremented. */group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}
}private native void start0();

分析源码发现threadStatus = 0,当调用本地方法start0()之后会改变threadStatus的值会改变,因此第二次调用start()方法时会报IllegalThreadStateException异常。

2、实现Runnable接口

在上面使用继承 Thread 类的方式来创建线程,但是我们知道在 Java 中是单继承,多实现。我们要是一个类继承了 Thread 类就不能显示的继承其它类了,那如果我们现在既要创建新的线程任务,也要继承其它类,那么我们要如何实现呢?

在 Thread 的构造器中,支持传递一个 Runnable 接口的实现类的方式创建线程。

Java多线程基础篇|JUC并发编程基础篇

class MyCustomThread implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}
public class Demo05 {public static void main(String[] args) {Thread thread = new Thread(new MyCustomThread());thread.start();}
}

我们还可以对以上的代码进行简化,比如写成匿名内部类对象的方式,还可以写成 lamdab 表达式的方式。

何为匿名内部类对象?

匿名内部类是指没有名字的内部类,它在声明时直接实现一个接口或继承自一个类,并且在使用时直接进行创建和实例化。匿名内部类在Java中非常常用,它可以简化代码的书写,使代码更为简洁,并且也可以使程序员在一定程度上隐藏代码的实现细节。

格式:

new 父类构造器<实参列表> implements 接口名<泛型参数列表>{ 外部类成员变量、方法;[内部类成员变量] [内部类方法]}

在 JDK 8 之后,如果不修改外部变量,可以直接访问,不用加 final 修饰。

public class OuterClass {public void myMethod() {final int x = 3; // 将x声明为finalnew Thread(new Runnable() {@Overridepublic void run() {x++; // 编译错误,无法修改x的值System.out.println(x);}}).start();}
}

匿名内部类对象:

public class Demo05 {public static void main(String[] args) {Thread thread = new Thread(new Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}});thread.start();}
}

要区分匿名对象,匿名内部类对象,内部类三者的区别。

匿名对象:没有给创建的对象起一个名字。

例如:new MyThread()

内部类:在一个类的内部定义的类。

例如:

class OutterClass {// 外部类成员class InnerClass {// 内部类成员}
}

匿名内部类对象:没有给内部类的实例起名字。

我们还可以进一步简化代码,写成 lamdab 表达式。

public class Demo05 {public static void main(String[] args) {new Thread(() -> System.out.println(Thread.currentThread().getName())).start();}
}

如果对 lamdab 不熟悉的同学,可以去网上搜索学习。

要使用 lamdab,使用的 JDK 版本要求大于等于8。


更多内容欢迎访问我的博客。