1.线程与进程
操作系统中运行的程序,就是一个进程。而线程便是进程的组成部分。
进程
一个进程就是一个执行的程序,而每一个进程都有自己独立的一块内存空间和一组系统资源。每一个进程的内部数据和状态都是完全独立的。进程是系统运行程序的基本单位,是系统进行资源分配和调度的基本单位,是操作系统的基础。进程是线程的容器。
线程
线程是程序执行流的最小单元,一个线程由线程id、当前指令指针和寄存器(堆栈)组成。是进程中的一个实体,是被系统独立调用和分配的基本单位,线程不拥有系统资源,只有运行中必不可少的资源,但可以同属一个进程和其他线程共享进程所拥有的的全部资源。
区别
- 启动的应用程序叫做 进程 ,再启动另一个应用程序,叫做两个进程;
- 线程 是进程内部在做的事情;
- 进程线程都作为基本的执行单元,线程是划分得比进程更小的执行单元;
- 线程是比进程小得多的抽象概念,一个进程拥有一个至多个线程;
- 每个进程都有一段专有的内存区域,而线程共享内存单元,通过共享内存单元实现数据交换、实时通信和必要的同步操作;
2.线程的生命周期
线程的生命周期包含5个阶段:
-
创建阶段(New)
-
就绪阶段(Runnable)
-
运行阶段(Running)
-
阻塞阶段(Blocked)
-
终止阶段(Dead)
-
创建阶段:当一个Thread类或者其子类的对象被声明并创建时,新生的线程对象属于 创建状态。
-
就绪阶段:处于创建阶段的线程执行start()方法后,进入线程队列等待CPU时间片,该状态具备了运行的状态,只是没有分配CPU资源。
-
运行阶段:当就绪阶段的线程分配到CPU资源,便进入运行阶段,run()方法定义了线程的操作。
-
阻塞阶段:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态。
-
终止阶段:当先成执行完自己的操作或被提前强制性的终止或出现异常导致结束,会进入终止状态。
3.创建线程
继承Thread类
通过继承Thread类,并重写其run()方法,run()方法即线程执行任务。创建后的子类通过调用start()方法即可执行线程方法。
public class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("run");
}
public static void main(String[] args) {
ThreadTest thread_test = new ThreadTest();
thread_test.start();
}
}
实现Runnable接口
此方法先定义一个类,实现Runnable接口,并重写接口run()方法,此run()方法是线程执行体,就是我们编写业务代码。接着创建Runnable实现类的对象,作为创建Thread对象的参数target,此Thread对象才是真正的线程对象(这里可能会有点疑问,什么是target 别急下面会提到)
public class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println("run");
}
public static void main(String[] args) {
RunnableTest runnable_test = new RunnableTest(); //创建一个RunnableTest实例
new Thread(runnable_test).start(); //启动线程
}
}
注意:runnable_test对象实现Runnablej接口,所以重写了run()方法,但直接调用run()方法,并不会直接启动一个新的线程,必须调用一个线程对象的start() 方法,才能启动一个线程。所以,在创建Thread对象的时候,把runnable_test作为构造方法的参数传递进去,这个线程启动的时候,就会去执行runnable_test.run()方法了。
关系
实际上这两种创建线程的方式,原理是一样的。为啥呢?
从Thread类源码可以看到:
Thread类实现了Runnable接口
再继续向下看,看下构造方法:
/**
* Allocates a new {@code Thread} object. This constructor has the same
* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
* {@code (null, null, gname)}, where {@code gname} is a newly generated
* name. Automatically generated names are of the form
* {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
*/
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
* Allocates a new {@code Thread} object. This constructor has the same
* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
* {@code (null, target, gname)}, where {@code gname} is a newly generated
* name. Automatically generated names are of the form
* {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
*
* @param target
* the object whose {@code run} method is invoked when this thread
* is started. If {@code null}, this classes {@code run} method does
* nothing.
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
翻译一下注释写的:
这两种构造方法是近似的,区别在调用初始化init()方法时,是否传入Runnable类对象target ,当继承Thread类时,已经定义target属性,这个Runnable类对象就是线程对象自己本身,而实现Runnable接口时,target就是实现接口的这个实例。所以说原理是一样的。
所以无论你使用Runnable还是Thread,都有一个new Thread的过程,调用本地方法启动一个线程,效果上最后都是实例化 Thread对象,然后在这个线程里执行目标对象的run()方法。
再看看Runnable接口,在Runnable接口的源码中:
翻译:
在大多数情况下,如果您仅打算覆盖run()方法而没有其他Thread方法,则应使用Runnable接口。 这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应将类归为子类。
所以很明显,如果要使用复杂的线程操作,就继承Thread类,简单就实现Runnable接口,因为Runnable接口只有一个run()的抽象方法
关于两种创建线程方法,资源是否共享说法的一点理解
看到说两种创建线程方法,区别有:Thread方法不能共享资源,Runnable可以。
关于这个结论肯定是有问题的,经常给出的例子是卖票问题。大概是说共有十张票,三个窗口同时卖票,一个Thread代表一个窗口。然后Thread方法会卖出去三十张票,而Runnable方法可以正确卖出去十张。
结合刚刚的构造方法源码可以很好理解,这里创建了三个不同的Thread对象,而run()方法是和其线程绑定的,一个线程启动一次run()任务,所以三个Thread对象,都运行了各自的run()自然是所谓的资源不共享的。
而Runnable看起来也是创建三个不同的Thread对象,但是创建三个不同Thread时,都是放入同一个Runnable对象,而这种创建方法run()是与Runnable对象绑定的,因为重写了Runnable的Run()方法,那自然是共享的啦,因为这三个线程都是执行同一个run()方法。
简单的说,他们其实没啥可比性,runnable相当于3个人一起卖10张票,thread是3个人分别卖10张票。这不是他们之间关键区别。真正关键还是写法上为了解决java类单继承的问题,实现接口方便。
问题:启动一个线程使用run()还是start()方法?
答案:
-
启动一个线程选择使用 start() 方法:
当用start()开始一个线程后,线程就进入就绪状态Runnable,这意味着Java虚拟接可以调度并执行,但不是意味着立刻执行,还需要CPU分配时间片,才会开始执行run()方法。 -
start()方法调用run()方法:
run()方法是必须重写的,run()中包含线程的主体(业务逻辑)
我的CSDN:https://blog.csdn.net/weixin_45910779/article/details/113727460