目录
一、进程与线程
1、什么是进程?什么是线程?
进程:
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志。
进程具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进行一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序,数据和进程控制块三部分组成;
线程:
在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
2、进程和线程是什么关系?
一个进程可以启动多个线程。
3、进程和线程的区别
1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
3. 进程之间相互独立,但同一进程下的各个线程之间堆内存和方法区内存共享,栈内存独立,一个线程一个栈。
4. 调度和切换:线程上下文切换比进程上下文切换要快得多
4、关于线程对象的生命周期?
新建状态:采用new语句创建完成
就绪状态:执行start后
运行状态:执行start后
阻塞状态:执行了wait语句、执行了sleep语句和等待某个对象锁,等待输入的场合
死亡状态:退出run()方法
二、实现线程的两种方式
java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。
1、概述实现线程的两种方式
【第一种方式】编写一个类,直接继承java.lang.Thread,重写run方法。
// 定义线程类
public class MyThread extends Thread{
public void run(){
}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程。
t.start();
【第二种方式】 编写一个类,实现java.lang.Runnable接口,实现run方法。
//定义一个可运行的类
public class MyRunnable implements Runnable {
public void run(){
}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
注意:
第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。
图示:
分析:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()方法调用的时候被创建,随着调用mt的对象的start方法,另外一个新的线程也启动了,这样这个应用就在多线程下运行。多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所有的栈内存空间,进行方法的压栈和弹栈。(使用了多线程机制之后, main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈)
当执行线程的任务结束了,线程自动在栈内存中释放了,但是当所有的执行线程都结束了,那么进程就结束了。
2、Thread类(创建线程方式一)
Java.lang.Thread类,API中该类定义了有关线程的一些方法,具体如下:
构造方法:
Public Thread(); 分配一个新的线程对象
Public Thread(String name); 分配一个指定名字的新的线程对象
Public Thread(Runnable target); 分配一个带有指定目标新的线程对象
Public Thread(Runnable target,String name); 分配一个带有指定目标新的线程对象并指定名字
【代码演示】
public class MyThread extends Thread {
// 重写run方法 完成该线程的执行逻辑
public void run(){
for(int i=0;i<20;i++){
System.out.println(i);
}
}
}
测试类:
public class Demo01 {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt = new MyThread();
//开启新线程
mt.start();
//在主方法中执行for循环
for(int i=0;i<20;i++){
System.out.println(i);
}
}
}
常用方法:
Public String getName();获取当前线程名字
Public void setName();设置当前线程的名字
Public void start(); 导致此线程开始执行,java虚拟机调用此线程的run方法
Public void run(); 此线程要执行的任务在此处定义代码
Public static void sleep(long millis); 使当前在执行的线程以指定的毫秒数暂停(暂时停止执行)
Public static Thread currentThread();返回当前正在执行的线程对象的引用。
3、实现Runnable接口(创建线程方式二)
Java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可
步骤如下:
- 定义runnable接口的实现类,并重写该接口的run方法,该run方法的方法体同样是该线程的线程执行体。
- 创建runnable实现类的实例,并一次实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start方法来启动线程
//这并不是一个线程类,只是一个可运行的类
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+""+i);
//Thread.currentThread()方法是调用Thread类中的静态方法currentThread()获取当前线程对象
}
}
}
//测试类
public class Demo {
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t = new Thread(mr, "xiao");
t.start();
for (int i=0;i<20;i++){
System.out.println("da"+i);
}
}
}
通过实现runnable接口,使得该类有了多线程类的特征,run方法是多线程程序的一个执行目标,所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target)创建出对象,然后调用Thread对象的start方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start方法来运行的,因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是要通过Thread类的对象,来控制线程的。
注意:
Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run方法仅仅作为线程执行体,而实际的线程对象依然是Thread类的实例,只是该Thread线程负责执行其target的run方法。
4、Thread和Runnable的区别
如果一个类继承Thread类,则不适合资源共享,但是如果实现了Runnable接口的话,则很容易实现资源共享。
总结:实现Runnable接口比继承Thread类所具有的优势有:
- 适合多个相同的程序代码的线程去共享一个资源
- 可以避免java中的单继承的局限性
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程相对独立了
- 线程池只能放入实现了Runnable接口或者Callable的类线程,不能直接放入继承Thread的类的线程。
注意:
在java中,每次程序执行至少要启动两个线程:一个main线程,一个垃圾回收机制
5、匿名内部类方式实现线程的创建
使用线程的匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。使用匿名内部类的方式实现runnable接口,重写runnable接口中run方法即可。
【代码演示】
public class NoNameInnerClassThread {
public static void main(String[] args) {
Runnable r = new Runnable(){
public void run(){
for(int i=0;i<20;i++){
System.out.println("李四"+i);
}
}
};
new Thread(r).start();
//主线程
for (int i=0;i<20;i++){
System.out.println("张三"+i);
}
}
}
三、实现线程的第三种方式---实现Callable接口
这种方式实现的线程是JDK8新特性,可以获取线程的返回值。之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。
优点:可以获取到线程的执行结果
缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
【代码演示】
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; // JUC包下的,属于java的并发包,*DK中没有这个包
public class ThreadTest{
public static void main(String[] args){
//第一步:创建一个"未来任务类"对象。
//参数非常重要,需要给一个Callable接口实现关对象。
FutureTask task = new FutureTask(new Callable(){
public Object call() throws Exception{ //call()方法相当于run方法, 只不过这个方法有返回值
//线程执行一个任旁,执行之后可能会有一个执行结果
//模拟执行
System.out.println("call method begin")
Thread.sleep(millis: 10 * 10);
System.out.println("call method end!");
int a=100;
int b=200;
return a+b; //自动装箱(int变成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.print1n("he11o wor1d!");
}
}