线程在java中占有举足轻重的地位,先从线程与进程谈起吧。
1.线程与进程的概念:
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
“同时”执行是人的感觉,在线程之间实际上轮换执行。
用户每启动一个进程,操作系统就会为该进程分配一个独立的内存空间。线程是进程中的一个实体,是被系统独立调度和分配的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。线程有就绪、阻塞和运行三种基本状态。
2. 线程的创建
2.1. 继承java.lang.Thread类
继承Thread类本质也是实现了Runnable接口的一个实例,启动线程的方法时调用start方法。在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
public class MyThread extends Thread{//继承Thread类
public void run(){
//重写run方法
}
}
public class MyTest{
public static void main(String[] args){
new MyThread().start();//创建并启动线程
}
}
2.2 实现java.lang.Runnable接口
定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体.
public class MyThread2 implements Runnable {//实现Runnable接口
public void run(){
//重写run方法
}
}
public class MyTest{
public static void main(String[] args){
//创建并启动线程
MyThread2 myThread=new MyThread2();
Thread thread=new Thread(myThread);
thread().start();
//或者 new Thread(new MyThread2()).start();
}
}
2.3 实现Callable接口(了解)
Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
可以有返回值,支持泛型的返回值
可以声明抛出异常
创建线程的三种方式的对比
1. 采用实现Runnable、Callable接口的方式创见多线程时,优势是:
由于java单继承多实现的特点,线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2. 使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
3. 线程的特点:
1、线程是轻量级的进程
2、线程没有独立的地址空间(内存空间)
3、线程是由进程创建的(寄生在进程)
4、一个进程可以拥有多个线程-->这就是我们常说的多线程编程
线程和进程的区别
区别 | 进程 | 线程 |
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销 | 线程可看做是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一个应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行时会为每个进程分配不同的内存区域 | 除CPU外,不会为线程分配内存(线程使用的资源是它所属的进程的资源),线程组只能共享资源 |
包含关系 | 没有线程的进程可以看作单线程,若一个进程内拥有多个线程,则执行过程是多条线共同完成的,多线程。 | 线城是进程的一部分,所有线程有时被称为是轻权进程或者轻量级进程 |
4.Thread类常用的方法
方法 | 功能 |
static Thread currentThread()
|
得到当前线程
|
getName() |
返回线程的名称
|
getId() | 方法的作用是取得线程的唯一标识。 |
setName(Stringname)
|
将线程的名称设置为由name指定的名称
|
int getPriority()
|
获得线程的优先级数值
|
void setPriority()
|
设置线程的优先级数值
|
void start()
|
调用run()方法启动线程,开始线程的执行
|
void run() |
存放线程体代码
|
isAlive() |
判断线程是否还“活”着,即线程是未终止
|
5. 线程的状态
新生状态 |
用
new
关键字建立一个线程对象后,该线程对象就处于新生状态。
处于新生状态的线程有自己的内存空间,通过调用
start
进入就绪状态
|
就绪状态 |
处于就绪状态线程具备了运行条件,但还没分配到
CPU
,处于线程就绪队列,等待系统为其分配
CPU
当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称之为“
cpu
调度”
|
运行状态 |
在运行状态的线程执行自己的
run
方法中代码,直到等待某资源而阻塞或完成任务而死亡
如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态
|
阻塞状态 |
处于运行状态的线程在某些情况下,如执行了
sleep
(睡眠)方法,或等待
I/O
设备等资源,将让出
CPU
并暂时停止自己的运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的
I/O
设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
|
死亡状态 |
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过执行stop
方法来终止一个线程
[
不推荐使用】,三是线程抛出未捕获的异常
|
6 线程的优先级
7. 线程控制方法
join() | 阻塞指定线程等到另一个线程完成以后再继续执行 |
sleep() | 使线程停止运行一段时间,将处于阻塞状态 如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行! |
yield() | 让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态 如果调用了yield方法之后,没有其他等待执行的线程,这个时候当前线程就会马上恢复执行! |
stop() | 结束线程,,不推荐使用 |
setDaemon() | 可以将指定的线程设置成后台线程 创建后台线程的线程结束时,后台线程也随之消亡 只能在线程启动之前把它设为后台线程 |
interrupt() | 并没有直接中断线程,而是需要被中断线程自己处理 |
wait() | 让出cpu使用权,释放锁。是Object类的方法 |