该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架。
1、线程概述
ASP站长网几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。
2、线程与进程
进程概述:
几乎所有的操作系统都支持进程的概念,所有运行中的任务通常对应一个进程( Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。
进程特征:
1、独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
2、动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的
3、并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
线程:
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
并发和并行:
并发:同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行
并行:同一时刻,有多条指令在多个处理器上同时执行
多线程:
概述:
多线程就是几乎同时执行多个线程(一个处理器在某一个时间点上永远都只能是一个线程!即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行。)。几乎同时是因为实际上多线程程序中的多个线程实际上是一个线程执行一会然后其他的线程再执行,并不是很多书籍所谓的同时执行。
多线程优点:
1、进程之间不能共享内存,但线程之间共享内存非常容易。
2、系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高
3、Java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程
3、使用多线程
多线程的创建:
(1)、继承Thread类:
第一步:定义Thread类的之类,并重写run方法,该run方法的方法体就代表了线程需要执行的任务
第二步:创建Thread类的实例
第三步:调用线程的start()方法来启动线程
public class FirstThread extends Thread {
private int i;
public void run() {
for(;i<100;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
//调用Thread的currentThread方法获取当前线程
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new FirstThread().start();
new FirstThread().start();
}
}
}
}
(2)、实现Runnable接口:
第一步:定义Runnable接口的实现类,并重写该接口的run方法,该run方法同样是线程需要执行的任务
第二步:创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
public class SecondThread implements Runnable {
private int i;
@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==20) {
SecondThread s1=new SecondThread();
new Thread(s1,"新线程1").start();;
new Thread(s1,"新线程2").start();
}
}
}
}
(3)、使用Callable和Future创建线程
细心的读者会发现,上面创建线程的两种方法。继承Thread和实现Runnable接口中的run都是没有返回值的。于是从Java5开始,Java提供了Callable接口,该接口是Runnable接口的增强版。Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。
创建并启动有返回值的线程的步骤如下:
第一步:创建 Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建 Callable实现类的实例。从Java8开始,可以直接使用 Lambda表达式创建 Callable对象
第二步:使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值
第三步:使用FutureTask对象作为Thread对象的target创建并启动新线程
第四步:通过FutureTask的get()方法获得子线程执行结束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThirdThread {
public static void main(String[] args) {
//ThirdThread rt=new ThirdThread();
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 i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循环变量i为"+i);
if(i==20) {
new Thread(task,"有返回值的线程").start();;
}
}
try {
System.out.println("子线程的返回值"+task.get());
}catch(Exception e) {
e.printStackTrace();
}
}
}
创建线程的三种方式的对比:
采用Runnable、Callable接口的方式创建多线程的优缺点:
优点:
1、线程类只是实现了 Runnable接口或 Callable接口,还可以继承其他类
2、在这种方式下,多个线程可以共享同一个 target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
缺点:
编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。
采用继承 Thread类的方式创建多线程的优缺点:
优点:
编写简单,如果需要访问当前线程,则无须使用 Thread.current Thread()方法,直接使用this即可获得当前线程
缺点:
因为线程已经继承了Thread类,所以不能再继承其他类
线程的生命周期:
新建和就绪状态:
当程序使用new关键字创建一个线程后,该线程就处于新建状态。
当线程对象调用了start()方法后,该线程就处于就绪状态。
运行和阻塞状态:
如果处于就绪状态的线程获取了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。
当线程调用sleep(),调用一个阻塞式IO方法,线程会被阻塞
死亡状态:
1、run()或者call()方法执行完成,线程正常结束
2、线程抛出一个未捕获的Exception或Error
3、直接调用该线程的stop方法来结束该线程——该方法容易导致死锁,不推荐使用